Skip to content

Commit

Permalink
Merge branch 'main' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
balazsorban44 committed Jun 10, 2021
2 parents ea9b6e3 + 832d51f commit 0c17af9
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 28 deletions.
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ jobs:
- name: Dependencies
uses: bahmutov/npm-install@v1
- name: Run tests
run: npm test
run: npm test -- --coverage --verbose
- name: Coverage
uses: codecov/codecov-action@v1
with:
directory: ./coverage
fail_ci_if_error: false
- name: Build
run: npm run build
release:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ app/yarn.lock

# Prisma migrations
/prisma/migrations

# Tests
/coverage
7 changes: 5 additions & 2 deletions config/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
transform: {
"\\.js$": ["babel-jest", { configFile: "./config/babel.config.js" }],
},
roots: ["../src"],
setupFilesAfterEnv: ["./jest-setup.js"],
rootDir: "../src",
setupFilesAfterEnv: ["../config/jest-setup.js"],
collectCoverageFrom: ["!client/__tests__/**"],
testMatch: ["**/*.test.js"],
coverageDirectory: "../coverage",
}
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"./errors": "./dist/lib/errors.js"
},
"scripts": {
"postinstall": "npx husky install",
"build": "npm run build:js && npm run build:css",
"build:js": "node ./config/build.js && babel --config-file ./config/babel.config.js src --out-dir dist",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
Expand Down
64 changes: 64 additions & 0 deletions src/client/__tests__/client-provider.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useState } from "react"
import { rest } from "msw"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockSession } from "./helpers/mocks"
import { Provider, useSession } from ".."
import userEvent from "@testing-library/user-event"

beforeAll(() => {
server.listen()
})

afterEach(() => {
jest.clearAllMocks()
server.resetHandlers()
})

afterAll(() => {
server.close()
})

test("fetches the session once and re-uses it for different consumers", async () => {
const sessionRouteCall = jest.fn()

server.use(
rest.get("/api/auth/session", (req, res, ctx) => {
sessionRouteCall()
res(ctx.status(200), ctx.json(mockSession))
})
)

render(<ProviderFlow />)

await waitFor(() => {
expect(sessionRouteCall).toHaveBeenCalledTimes(1)

const session1 = screen.getByTestId("session-consumer-1").textContent
const session2 = screen.getByTestId("session-consumer-2").textContent

expect(session1).toEqual(session2)
})
})

function ProviderFlow({ options = {} }) {
return (
<>
<Provider options={options}>
<SessionConsumer />
<SessionConsumer testId="2" />
</Provider>
</>
)
}

function SessionConsumer({ testId = 1 }) {
const [session, loading] = useSession()

if (loading) return <span>loading</span>

return (
<div data-testid={`session-consumer-${testId}`}>
{JSON.stringify(session)}
</div>
)
}
105 changes: 105 additions & 0 deletions src/client/__tests__/csrf.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockCSRFToken } from "./helpers/mocks"
import logger from "../../lib/logger"
import { getCsrfToken } from ".."
import { rest } from "msw"

jest.mock("../../lib/logger", () => ({
__esModule: true,
default: {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
},
proxyLogger(logger) {
return logger
},
}))

beforeAll(() => {
server.listen()
})

afterEach(() => {
server.resetHandlers()
jest.clearAllMocks()
})

afterAll(() => {
server.close()
})

test("returns the Cross Site Request Forgery Token (CSRF Token) required to make POST requests", async () => {
render(<CSRFFlow />)

userEvent.click(screen.getByRole("button"))

await waitFor(() => {
expect(screen.getByTestId("csrf-result").textContent).toEqual(
mockCSRFToken.csrfToken
)
})
})

test("when there's no CSRF token returned, it'll reflect that", async () => {
server.use(
rest.get("/api/auth/csrf", (req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
...mockCSRFToken,
csrfToken: null,
})
)
)
)

render(<CSRFFlow />)

userEvent.click(screen.getByRole("button"))

await waitFor(() => {
expect(screen.getByTestId("csrf-result").textContent).toBe("null-response")
})
})

test("when the fetch fails it'll throw a client fetch error", async () => {
server.use(
rest.get("/api/auth/csrf", (req, res, ctx) =>
res(ctx.status(500), ctx.text("some error happened"))
)
)

render(<CSRFFlow />)

userEvent.click(screen.getByRole("button"))

await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith(
"CLIENT_FETCH_ERROR",
"csrf",
new SyntaxError("Unexpected token s in JSON at position 0")
)
})
})

function CSRFFlow() {
const [response, setResponse] = useState()

async function handleCSRF() {
const result = await getCsrfToken()
setResponse(result)
}

return (
<>
<p data-testid="csrf-result">
{response === null ? "null-response" : response || "no response"}
</p>
<button onClick={handleCSRF}>Get CSRF</button>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { rest } from "msw"
import { randomBytes } from "crypto"

export const mockSession = {
ok: true,
user: {
image: null,
name: "John",
Expand All @@ -12,6 +13,7 @@ export const mockSession = {
}

export const mockProviders = {
ok: true,
github: {
id: "github",
name: "Github",
Expand All @@ -34,6 +36,7 @@ export const mockProviders = {
}

export const mockCSRFToken = {
ok: true,
csrfToken: randomBytes(32).toString("hex"),
}

Expand Down
File renamed without changes.
85 changes: 85 additions & 0 deletions src/client/__tests__/providers.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { useState } from "react"
import userEvent from "@testing-library/user-event"
import { render, screen, waitFor } from "@testing-library/react"
import { server, mockProviders } from "./helpers/mocks"
import { getProviders } from ".."
import logger from "../../lib/logger"
import { rest } from "msw"

jest.mock("../../lib/logger", () => ({
__esModule: true,
default: {
warn: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
},
proxyLogger(logger) {
return logger
},
}))

beforeAll(() => {
server.listen()
})

afterEach(() => {
server.resetHandlers()
jest.clearAllMocks()
})

afterAll(() => {
server.close()
})

test("when called it'll return the currently configured providers for sign in", async () => {
render(<ProvidersFlow />)

userEvent.click(screen.getByRole("button"))

await waitFor(() => {
expect(screen.getByTestId("providers-result").textContent).toEqual(
JSON.stringify(mockProviders)
)
})
})

test("when failing to fetch the providers, it'll log the error", async () => {
server.use(
rest.get("/api/auth/providers", (req, res, ctx) =>
res(ctx.status(500), ctx.text("some error happened"))
)
)

render(<ProvidersFlow />)

userEvent.click(screen.getByRole("button"))

await waitFor(() => {
expect(logger.error).toHaveBeenCalledTimes(1)
expect(logger.error).toBeCalledWith(
"CLIENT_FETCH_ERROR",
"providers",
new SyntaxError("Unexpected token s in JSON at position 0")
)
})
})

function ProvidersFlow() {
const [response, setResponse] = useState()

async function handleGerProviders() {
const result = await getProviders()
setResponse(result)
}

return (
<>
<p data-testid="providers-result">
{response === null
? "null-response"
: JSON.stringify(response) || "no response"}
</p>
<button onClick={handleGerProviders}>Get Providers</button>
</>
)
}
17 changes: 9 additions & 8 deletions src/client/__tests__/session.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { render, screen, waitFor } from "@testing-library/react"
import { rest } from "msw"
import { server, mockSession } from "./mocks"
import { server, mockSession } from "./helpers/mocks"
import logger from "../../lib/logger"
import { useState, useEffect } from "react"
import { getSession } from ".."
import { getBroadcastEvents } from "./utils"
import { getBroadcastEvents } from "./helpers/utils"

jest.mock("../../lib/logger", () => ({
__esModule: true,
Expand All @@ -27,10 +27,12 @@ beforeEach(() => {

afterEach(() => {
server.resetHandlers()
jest.restoreAllMocks()
jest.clearAllMocks()
})

afterAll(() => server.close())
afterAll(() => {
server.close()
})

test("if it can fetch the session, it should store it in `localStorage`", async () => {
render(<SessionFlow />)
Expand Down Expand Up @@ -81,7 +83,7 @@ function SessionFlow() {
useEffect(() => {
async function fetchUserSession() {
try {
const result = await getSession({})
const result = await getSession()
setSession(result)
} catch (e) {
console.error(e)
Expand All @@ -90,8 +92,7 @@ function SessionFlow() {
fetchUserSession()
}, [])

if (session) {
return <pre>{JSON.stringify(session, null, 2)}</pre>
}
if (session) return <pre>{JSON.stringify(session, null, 2)}</pre>

return <p>No session</p>
}
6 changes: 3 additions & 3 deletions src/client/__tests__/sign-in.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
mockCredentialsResponse,
mockEmailResponse,
mockGithubResponse,
} from "./mocks"
} from "./helpers/mocks"
import { signIn } from ".."
import { rest } from "msw"

Expand Down Expand Up @@ -36,7 +36,7 @@ beforeAll(() => {
})

beforeEach(() => {
jest.resetAllMocks()
jest.clearAllMocks()
server.resetHandlers()
})

Expand Down Expand Up @@ -284,7 +284,7 @@ function SignInFlow({
<p data-testid="signin-result">
{response ? JSON.stringify(response) : "no response"}
</p>
<button onClick={() => handleSignIn()}>Sign in</button>
<button onClick={handleSignIn}>Sign in</button>
</>
)
}
Loading

0 comments on commit 0c17af9

Please sign in to comment.