Thanks to visit codestin.com
Credit goes to github.com

Skip to content

chore(site): Add unit tests, mocks #514

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 23, 2022
76 changes: 76 additions & 0 deletions site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from "react"
import { Routes, Route } from "react-router-dom"
import { RequireAuth, AuthAndNav } from "./components"
import { IndexPage } from "./pages"
import { NotFoundPage } from "./pages/404"
import { CliAuthenticationPage } from "./pages/cli-auth"
import { HealthzPage } from "./pages/healthz"
import { SignInPage } from "./pages/login"
import { ProjectsPage } from "./pages/projects"
import { ProjectPage } from "./pages/projects/[organization]/[project]"
import { CreateWorkspacePage } from "./pages/projects/[organization]/[project]/create"
import { WorkspacePage } from "./pages/workspaces/[workspace]"

export const AppRouter: React.FC = () => (
<Routes>
<Route path="/">
<Route
index
element={
<RequireAuth>
<IndexPage />
</RequireAuth>
}
/>

<Route path="login" element={<SignInPage />} />
<Route path="healthz" element={<HealthzPage />} />
<Route path="cli-auth" element={<CliAuthenticationPage />} />

<Route path="projects">
<Route
index
element={
<AuthAndNav>
<ProjectsPage />
</AuthAndNav>
}
/>
<Route path=":organization/:project">
<Route
index
element={
<AuthAndNav>
<ProjectPage />
</AuthAndNav>
}
/>
<Route
path="create"
element={
<RequireAuth>
<CreateWorkspacePage />
</RequireAuth>
}
/>
</Route>
</Route>

<Route path="workspaces">
<Route
path=":workspace"
element={
<AuthAndNav>
<WorkspacePage />
</AuthAndNav>
}
/>
</Route>

{/* Using path="*"" means "match anything", so this route
acts like a catch-all for URLs that we don't have explicit
routes for. */}
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
)
20 changes: 0 additions & 20 deletions site/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,6 @@ export const provisioners: Types.Provisioner[] = [
},
]

export namespace Project {
export const create = async (request: Types.CreateProjectRequest): Promise<Types.Project> => {
const response = await fetch(`/api/v2/projects/${request.organizationId}/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
})

const body = await response.json()
await mutate("/api/v2/projects")
if (!response.ok) {
throw new Error(body.message)
}

return body
}
}

export namespace Workspace {
export const create = async (request: Types.CreateWorkspaceRequest): Promise<Types.Workspace> => {
const response = await fetch(`/api/v2/users/me/workspaces`, {
Expand Down
76 changes: 3 additions & 73 deletions site/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,10 @@ import CssBaseline from "@material-ui/core/CssBaseline"
import ThemeProvider from "@material-ui/styles/ThemeProvider"
import { SWRConfig } from "swr"
import { light } from "./theme"
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
import { BrowserRouter as Router } from "react-router-dom"

import { CliAuthenticationPage } from "./pages/cli-auth"
import { NotFoundPage } from "./pages/404"
import { IndexPage } from "./pages/index"
import { SignInPage } from "./pages/login"
import { ProjectsPage } from "./pages/projects"
import { ProjectPage } from "./pages/projects/[organization]/[project]"
import { CreateWorkspacePage } from "./pages/projects/[organization]/[project]/create"
import { WorkspacePage } from "./pages/workspaces/[workspace]"
import { HealthzPage } from "./pages/healthz"
import { AuthAndNav, RequireAuth } from "./components/Page"
import { XServiceProvider } from "./xServices/StateContext"
import { AppRouter } from "./AppRouter"
import "./theme/global-fonts"

export const App: React.FC = () => {
Expand All @@ -42,68 +33,7 @@ export const App: React.FC = () => {
<XServiceProvider>
<ThemeProvider theme={light}>
<CssBaseline />

<Routes>
<Route path="/">
<Route
index
element={
<RequireAuth>
<IndexPage />
</RequireAuth>
}
/>

<Route path="login" element={<SignInPage />} />
<Route path="healthz" element={<HealthzPage />} />
<Route path="cli-auth" element={<CliAuthenticationPage />} />

<Route path="projects">
<Route
index
element={
<AuthAndNav>
<ProjectsPage />
</AuthAndNav>
}
/>
<Route path=":organization/:project">
<Route
index
element={
<AuthAndNav>
<ProjectPage />
</AuthAndNav>
}
/>
<Route
path="create"
element={
<RequireAuth>
<CreateWorkspacePage />
</RequireAuth>
}
/>
</Route>
</Route>

<Route path="workspaces">
<Route
path=":workspace"
element={
<AuthAndNav>
<WorkspacePage />
</AuthAndNav>
}
/>
</Route>

{/* Using path="*"" means "match anything", so this route
acts like a catch-all for URLs that we don't have explicit
routes for. */}
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
<AppRouter />
</ThemeProvider>
</XServiceProvider>
</SWRConfig>
Expand Down
4 changes: 3 additions & 1 deletion site/src/components/Page/RequireAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useActor } from "@xstate/react"
import React, { useContext } from "react"
import { Navigate, useLocation } from "react-router"
import { embedRedirect } from "../../util/redirect"
import { XServiceContext } from "../../xServices/StateContext"
import { FullScreenLoader } from "../Loader/FullScreenLoader"

Expand All @@ -12,9 +13,10 @@ export const RequireAuth: React.FC<RequireAuthProps> = ({ children }) => {
const xServices = useContext(XServiceContext)
const [userState] = useActor(xServices.userXService)
const location = useLocation()
const redirectTo = embedRedirect(location.pathname)

if (userState.matches("signedOut") || !userState.context.me) {
return <Navigate to={"/login?redirect=" + encodeURIComponent(location.pathname)} />
return <Navigate to={redirectTo} />
} else if (userState.hasTag("loading")) {
return <FullScreenLoader />
} else {
Expand Down
4 changes: 2 additions & 2 deletions site/src/components/SignIn/SignInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const SignInForm: React.FC<SignInFormProps> = ({ isLoading, authErrorMess
<form onSubmit={form.handleSubmit}>
<div>
<FormTextField
label="Email"
autoComplete="email"
autoFocus
className={styles.loginTextField}
Expand All @@ -71,10 +72,10 @@ export const SignInForm: React.FC<SignInFormProps> = ({ isLoading, authErrorMess
inputProps={{
id: "signin-form-inpt-email",
}}
placeholder="Email"
variant="outlined"
/>
<FormTextField
label="Password"
autoComplete="current-password"
className={styles.loginTextField}
form={form}
Expand All @@ -84,7 +85,6 @@ export const SignInForm: React.FC<SignInFormProps> = ({ isLoading, authErrorMess
id: "signin-form-inpt-password",
}}
isPassword
placeholder="Password"
variant="outlined"
/>
{authErrorMessage && (
Expand Down
12 changes: 2 additions & 10 deletions site/src/pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { useActor } from "@xstate/react"
import React, { useContext } from "react"
import { SignInForm } from "./../components/SignIn"
import { Navigate, useLocation } from "react-router-dom"
import { Location } from "history"
import { XServiceContext } from "../xServices/StateContext"
import { retrieveRedirect } from "../util/redirect"

export const useStyles = makeStyles((theme) => ({
root: {
Expand All @@ -20,21 +20,13 @@ export const useStyles = makeStyles((theme) => ({
},
}))

const getRedirectFromLocation = (location: Location) => {
const defaultRedirect = "/"

const searchParams = new URLSearchParams(location.search)
const redirect = searchParams.get("redirect")
return redirect ? redirect : defaultRedirect
}

export const SignInPage: React.FC = () => {
const styles = useStyles()
const location = useLocation()
const xServices = useContext(XServiceContext)
const [userState, userSend] = useActor(xServices.userXService)
const isLoading = userState.hasTag("loading")
const redirectTo = getRedirectFromLocation(location)
const redirectTo = retrieveRedirect(location.search)
const authErrorMessage = userState.context.authError ? (userState.context.authError as Error).message : undefined

const onSubmit = async ({ email, password }: { email: string; password: string }) => {
Expand Down
24 changes: 12 additions & 12 deletions site/src/test_helpers/entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,39 @@ export const MockSessionToken = { session_token: "my-session-token" }
export const MockAPIKey = { key: "my-api-key" }

export const MockUser: UserResponse = {
id: "test-user-id",
id: "test-user",
username: "TestUser",
email: "[email protected]",
created_at: "",
}

export const MockProject: Project = {
id: "project-id",
export const MockOrganization: Organization = {
id: "test-org",
name: "Test Organization",
created_at: "",
updated_at: "",
organization_id: "test-org",
name: "Test Project",
provisioner: "test-provisioner",
active_version_id: "",
}

export const MockProvisioner: Provisioner = {
id: "test-provisioner",
name: "Test Provisioner",
}

export const MockOrganization: Organization = {
id: "test-org",
name: "Test Organization",
export const MockProject: Project = {
id: "test-project",
created_at: "",
updated_at: "",
organization_id: MockOrganization.id,
name: "Test Project",
provisioner: MockProvisioner.id,
active_version_id: "",
}

export const MockWorkspace: Workspace = {
id: "test-workspace",
name: "Test-Workspace",
created_at: "",
updated_at: "",
project_id: "project-id",
owner_id: "test-user-id",
project_id: MockProject.id,
owner_id: MockUser.id,
}
22 changes: 22 additions & 0 deletions site/src/test_helpers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,26 @@ import { rest } from "msw"
import * as M from "./entities"

export const handlers = [
// organizations
rest.get("/api/v2/organizations/:organizationId", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockOrganization))
}),
rest.get("/api/v2/organizations/:organizationId/projects/:projectId", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockProject))
}),

// projects
rest.get("/api/v2/projects/:projectId", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockProject))
}),

// users
rest.post("/api/v2/users/me/workspaces", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockWorkspace))
}),
rest.get("/api/v2/users/me/organizations/:organizationId", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockOrganization))
}),
rest.post("/api/v2/users/login", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockSessionToken))
}),
Expand All @@ -17,4 +34,9 @@ export const handlers = [
rest.get("/api/v2/users/me/keys", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockAPIKey))
}),

// workspaces
rest.get("/api/v2/workspaces/:workspaceId", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockWorkspace))
}),
]
20 changes: 20 additions & 0 deletions site/src/util/redirect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { embedRedirect, retrieveRedirect } from "./redirect"

describe("redirect helper functions", () => {
describe("embedRedirect", () => {
it("embeds the page to return to in the URL", () => {
const result = embedRedirect("/workspaces", "/page")
expect(result).toEqual("/page?redirect=%2Fworkspaces")
})
it("defaults to navigating to the login page", () => {
const result = embedRedirect("/workspaces")
expect(result).toEqual("/login?redirect=%2Fworkspaces")
})
})
describe("retrieveRedirect", () => {
it("retrieves the page to return to from the URL", () => {
const result = retrieveRedirect("?redirect=%2Fworkspaces")
expect(result).toEqual("/workspaces")
})
})
})
Loading