Sync to main (#26)
* Fix sign up form bug * Add run.sh to run setup and run web app * Output node version * Update readme with run.sh * Fix file upload form to handle ota_update service * Enable file upload form Enable error boundary to catch React errors (#7) Fix warning for link noreferrer Include authorization header with file upload * Remove default localhost settings (#8) * Remove default localhost settings Replace with deployment settings * Fix for upload data format * Fix test data for last commit * Fix json link format and remove localhost default settings (#10) * Remove default localhost settings Replace with deployment settings * Fix for upload data format * Fix test data for last commit * Fix link data format * Fix link json again (#12) Use id token instead of access token * nginx things * Web Worker Sign Out and Use Go API (#13) * Calculate checksum and send with file upload * Limit file upload and display rejected file error * Add sign in timeout * Check auth token structure before setting Clean up * Use web worker timer to sign out Remove checksum Point to Go ota update * Remove checksum dependency * Use compute auth service and fix static code analyzer warnings (#15) * Clean up formatting * Use new compute_auth service Implment SSO Implement token refresh Clean up unit tests * Fix unit tests * Fix auth test Fix warnings * Update default settings for compute_auth * Change main UI layout and add VINs to add and upload forms (#16) * Add new upload update package form Add new add vehicle form Add new side menu layout Add new toolbar layout Update and add unit tests * Enable add get and add vehicles * Integration issues with ota_update service * Update get vehicle JSON format * Fix related unit test Add release notes field * Add StatusContext to display error and status messages * Handle api error json (#18) * Handle api error json * Fix get vehicles error handling Update .env.template * Fix signout refresh (#20) * Merge to main (#17) * Fix sign up form bug * Add run.sh to run setup and run web app * Output node version * Update readme with run.sh * Fix file upload form to handle ota_update service * Enable file upload form Enable error boundary to catch React errors (#7) Fix warning for link noreferrer Include authorization header with file upload * Remove default localhost settings (#8) * Remove default localhost settings Replace with deployment settings * Fix for upload data format * Fix test data for last commit * Fix json link format and remove localhost default settings (#10) * Remove default localhost settings Replace with deployment settings * Fix for upload data format * Fix test data for last commit * Fix link data format * Fix link json again (#12) Use id token instead of access token * nginx things * Web Worker Sign Out and Use Go API (#13) * Calculate checksum and send with file upload * Limit file upload and display rejected file error * Add sign in timeout * Check auth token structure before setting Clean up * Use web worker timer to sign out Remove checksum Point to Go ota update * Remove checksum dependency * Use compute auth service and fix static code analyzer warnings (#15) * Clean up formatting * Use new compute_auth service Implment SSO Implement token refresh Clean up unit tests * Fix unit tests * Fix auth test Fix warnings * Update default settings for compute_auth * Change main UI layout and add VINs to add and upload forms (#16) * Add new upload update package form Add new add vehicle form Add new side menu layout Add new toolbar layout Update and add unit tests * Enable add get and add vehicles * Integration issues with ota_update service * Update get vehicle JSON format * Fix related unit test Add release notes field * Add StatusContext to display error and status messages * Handle api error json (#18) * Handle api error json * Fix get vehicles error handling Update .env.template Co-authored-by: Rafi Greenberg <rgreenberg@fiskerinc.com> * Fix sign out and refresh * Check for bad json Co-authored-by: Rafi Greenberg <rgreenberg@fiskerinc.com> * Add role checks (#21) * Add role checks * Remove moved Roles enum * Add package updates, car updates, and vehicle screens (#25) Co-authored-by: Rafi Greenberg <rgreenberg@fiskerinc.com>
This commit is contained in:
@@ -38,10 +38,6 @@ export const FileUploadProvider = ({ children }) => {
|
||||
throw new Error("Package update version required");
|
||||
}
|
||||
|
||||
if (!formData.vehicles || formData.vehicles.length === 0) {
|
||||
throw new Error("Vehicles required");
|
||||
}
|
||||
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
throw new Error("File required");
|
||||
}
|
||||
@@ -64,7 +60,7 @@ export const FileUploadProvider = ({ children }) => {
|
||||
setStatus(`Uploading ${filename}`);
|
||||
setCancelUpload(getCancelToken());
|
||||
|
||||
const { data } = await uploadFile(
|
||||
const data = await uploadFile(
|
||||
file,
|
||||
formData,
|
||||
accessToken,
|
||||
|
||||
@@ -33,7 +33,6 @@ describe("FileUploadContext", () => {
|
||||
linkURL,
|
||||
upload,
|
||||
cancel,
|
||||
setFiles,
|
||||
} = useFileUploadContext();
|
||||
const { message, setMessage } = useStatusContext();
|
||||
const TEST_FILE = [{ name: "test.jpg", size: 0 }];
|
||||
|
||||
291
src/components/Contexts/UpdateContext.test.jsx
Normal file
291
src/components/Contexts/UpdateContext.test.jsx
Normal file
@@ -0,0 +1,291 @@
|
||||
jest.mock("../../services/updates");
|
||||
|
||||
import {
|
||||
render,
|
||||
cleanup,
|
||||
screen,
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from "@testing-library/react";
|
||||
import { UpdatesProvider, useUpdatesContext } from "../Contexts/UpdatesContext";
|
||||
import { StatusProvider, useStatusContext } from "../Contexts/StatusContext";
|
||||
import { TEST_AUTH_OBJECT } from "../../utils/testing";
|
||||
|
||||
describe("UpdatesContext", () => {
|
||||
describe("getPackages", () => {
|
||||
const expectedData = `[{"id":1,"package_name":"Test","version":"1.0","link":"http://cloudfront.com/download"},{"id":2,"package_name":"Test","version":"1.1","link":"http://cloudfront.com/download"},{"id":3,"package_name":"Test","version":"1.2","link":"http://cloudfront.com/download"}]`;
|
||||
const checkState = (busy, packages, message) => {
|
||||
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
||||
expect(screen.getByTestId("packages").innerHTML).toEqual(packages);
|
||||
expect(screen.getByTestId("message").innerHTML).toEqual(message);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { busy, packages, getPackages } = useUpdatesContext();
|
||||
const { message, setMessage } = useStatusContext();
|
||||
const exec = async (data, token) => {
|
||||
try {
|
||||
await getPackages(data, token);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="busy">{busy.toString()}</div>
|
||||
<div data-testid="packages">{JSON.stringify(packages)}</div>
|
||||
<div data-testid="message">{message}</div>
|
||||
<button
|
||||
data-testid="no-filter"
|
||||
onClick={() => {
|
||||
exec(null, TEST_AUTH_OBJECT);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
data-testid="with-filter"
|
||||
onClick={() => {
|
||||
exec({ package_name: "Test" }, TEST_AUTH_OBJECT);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(
|
||||
<StatusProvider>
|
||||
<UpdatesProvider>
|
||||
<TestComp />
|
||||
</UpdatesProvider>
|
||||
</StatusProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("Initial state", async () => {
|
||||
checkState("false", "[]", "");
|
||||
});
|
||||
|
||||
it("getPackages no filter", async () => {
|
||||
fireEvent.click(screen.getByTestId("no-filter"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
||||
);
|
||||
checkState("false", expectedData, "");
|
||||
});
|
||||
|
||||
it("getPackages with filter", async () => {
|
||||
fireEvent.click(screen.getByTestId("with-filter"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
||||
);
|
||||
checkState("false", expectedData, "");
|
||||
});
|
||||
});
|
||||
|
||||
describe("updatePackage", () => {
|
||||
let result = null;
|
||||
const checkState = (busy, message, data) => {
|
||||
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
||||
expect(screen.getByTestId("message").innerHTML).toEqual(message);
|
||||
expect(result).toEqual(data);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { busy, updatePackage } = useUpdatesContext();
|
||||
const { message, setMessage } = useStatusContext();
|
||||
const exec = async (data, token) => {
|
||||
var r = null;
|
||||
|
||||
try {
|
||||
r = await updatePackage(data, token);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="busy">{busy.toString()}</div>
|
||||
<div data-testid="message">{message}</div>
|
||||
<button
|
||||
data-testid="no-data"
|
||||
onClick={async () => {
|
||||
result = await exec(null, TEST_AUTH_OBJECT);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
data-testid="with-bad-data"
|
||||
onClick={async () => {
|
||||
result = await exec({ package_name: "Test" }, TEST_AUTH_OBJECT);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
data-testid="with-good-data"
|
||||
onClick={async () => {
|
||||
result = await exec(
|
||||
{
|
||||
package_name: "Test",
|
||||
version: "1.0",
|
||||
},
|
||||
TEST_AUTH_OBJECT
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(
|
||||
<StatusProvider>
|
||||
<UpdatesProvider>
|
||||
<TestComp />
|
||||
</UpdatesProvider>
|
||||
</StatusProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
result = null;
|
||||
});
|
||||
|
||||
it("Initial state", () => {
|
||||
checkState("false", "", null);
|
||||
});
|
||||
|
||||
it("no-data", async () => {
|
||||
fireEvent.click(screen.getByTestId("no-data"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
||||
);
|
||||
checkState("false", "No update data", null);
|
||||
});
|
||||
|
||||
it("with-bad-data", async () => {
|
||||
fireEvent.click(screen.getByTestId("with-bad-data"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
||||
);
|
||||
checkState("false", "Version required", null);
|
||||
});
|
||||
|
||||
it("with-good-data", async () => {
|
||||
fireEvent.click(screen.getByTestId("with-good-data"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
||||
);
|
||||
checkState("false", "", { package_name: "Test", version: "1.0" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("createCarUpdates", () => {
|
||||
let result = null;
|
||||
const checkState = (busy, message, data) => {
|
||||
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
||||
expect(screen.getByTestId("message").innerHTML).toEqual(message);
|
||||
expect(result).toEqual(data);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { busy, createCarUpdates } = useUpdatesContext();
|
||||
const { message, setMessage } = useStatusContext();
|
||||
const exec = async (data, token) => {
|
||||
var r = null;
|
||||
|
||||
try {
|
||||
r = await createCarUpdates(data, token);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="busy">{busy.toString()}</div>
|
||||
<div data-testid="message">{message}</div>
|
||||
<button
|
||||
data-testid="no-data"
|
||||
onClick={async () => {
|
||||
result = await exec(null, TEST_AUTH_OBJECT);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
data-testid="with-bad-data"
|
||||
onClick={async () => {
|
||||
result = await exec(
|
||||
{ package_id: 1, car_ids: [] },
|
||||
TEST_AUTH_OBJECT
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
data-testid="with-good-data"
|
||||
onClick={async () => {
|
||||
result = await exec(
|
||||
{
|
||||
package_id: 1,
|
||||
car_ids: [1, 2, 3],
|
||||
},
|
||||
TEST_AUTH_OBJECT
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
render(
|
||||
<StatusProvider>
|
||||
<UpdatesProvider>
|
||||
<TestComp />
|
||||
</UpdatesProvider>
|
||||
</StatusProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
result = null;
|
||||
});
|
||||
|
||||
it("Initial state", () => {
|
||||
checkState("false", "", null);
|
||||
});
|
||||
|
||||
it("no-data", async () => {
|
||||
fireEvent.click(screen.getByTestId("no-data"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
||||
);
|
||||
checkState("false", "No car update data", null);
|
||||
});
|
||||
|
||||
it("with-bad-data", async () => {
|
||||
fireEvent.click(screen.getByTestId("with-bad-data"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
||||
);
|
||||
checkState("false", "Car ids required", null);
|
||||
});
|
||||
|
||||
it("with-good-data", async () => {
|
||||
fireEvent.click(screen.getByTestId("with-good-data"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
||||
);
|
||||
checkState("false", "", {
|
||||
id: 1,
|
||||
package_id: 1,
|
||||
car_ids: [1, 2, 3],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
131
src/components/Contexts/UpdatesContext.jsx
Normal file
131
src/components/Contexts/UpdatesContext.jsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
|
||||
import api from "../../services/updates";
|
||||
|
||||
const UpdatesContext = React.createContext();
|
||||
|
||||
export const UpdatesProvider = ({ children }) => {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [packages, setPackages] = useState([]);
|
||||
const [carUpdates, setCarUpdates] = useState([]);
|
||||
const [totalPackages, setTotalPackages] = useState(0);
|
||||
const [totalCarUpdates, setTotalCarUpdates] = useState(0);
|
||||
|
||||
const getPackages = async (search, token) => {
|
||||
let result;
|
||||
|
||||
try {
|
||||
setBusy(true);
|
||||
result = await api.getPackages(search, token);
|
||||
if (result.error)
|
||||
throw new Error(`Get packages error. ${result.message}`);
|
||||
setPackages(result.data);
|
||||
if (search && search.offset === 0 && result.total) {
|
||||
setTotalPackages(result.total);
|
||||
}
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const updatePackage = async (data, token) => {
|
||||
let result = null;
|
||||
|
||||
try {
|
||||
setBusy(true);
|
||||
validateUpdatePackage(data);
|
||||
result = await api.updatePackage(data, token);
|
||||
if (result.error)
|
||||
throw new Error(`Update package error. ${result.message}`);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const createCarUpdates = async (data, token) => {
|
||||
let result;
|
||||
|
||||
try {
|
||||
setBusy(true);
|
||||
validateCreateCarUpdates(data);
|
||||
result = await api.createCarUpdates(data, token);
|
||||
if (result.error)
|
||||
throw new Error(`Create car updates error. ${result.message}`);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getCarUpdates = async (search, token) => {
|
||||
let result;
|
||||
|
||||
try {
|
||||
setBusy(true);
|
||||
result = await api.getCarUpdates(search, token);
|
||||
if (result.error)
|
||||
throw new Error(`Get packages error. ${result.message}`);
|
||||
setCarUpdates(result.data);
|
||||
if (search && search.offset === 0 && result.total) {
|
||||
setTotalCarUpdates(result.total);
|
||||
}
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
return (
|
||||
<UpdatesContext.Provider
|
||||
value={{
|
||||
busy,
|
||||
packages,
|
||||
totalPackages,
|
||||
carUpdates,
|
||||
totalCarUpdates,
|
||||
getPackages,
|
||||
updatePackage,
|
||||
createCarUpdates,
|
||||
getCarUpdates,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UpdatesContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useUpdatesContext = () => useContext(UpdatesContext);
|
||||
|
||||
const validateUpdatePackage = (data) => {
|
||||
if (data === null) {
|
||||
throw new Error("No update data");
|
||||
}
|
||||
|
||||
if (!data.package_name) {
|
||||
throw new Error("Package name required");
|
||||
}
|
||||
|
||||
if (!data.version) {
|
||||
throw new Error("Version required");
|
||||
}
|
||||
};
|
||||
|
||||
const validateCreateCarUpdates = (data) => {
|
||||
if (data === null) {
|
||||
throw new Error("No car update data");
|
||||
}
|
||||
|
||||
if (!data.package_id || data.package_id === 0) {
|
||||
throw new Error("Package id required");
|
||||
}
|
||||
|
||||
if (!data.car_ids || data.car_ids.length === 0) {
|
||||
throw new Error("Car ids required");
|
||||
}
|
||||
};
|
||||
@@ -1,21 +1,29 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import auth from "../../services/auth";
|
||||
import getTimerWorker from "../../services/timer";
|
||||
import { parsePayload } from "../../utils/jwt";
|
||||
import { getGroups } from "../../utils/roles";
|
||||
|
||||
const UserContext = React.createContext();
|
||||
|
||||
export const UserProvider = ({ children }) => {
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const [token, setToken] = useState(null);
|
||||
const [groups, setGroups] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
let timer;
|
||||
|
||||
useEffect(() => {
|
||||
if (!localStorage) return;
|
||||
const t = JSON.parse(localStorage.getItem("token"));
|
||||
if (!t || !t.idToken || !t.idToken.jwtToken) return;
|
||||
if (!t.idToken.payload || !t.idToken.payload.exp) return;
|
||||
setToken(t);
|
||||
try {
|
||||
if (!localStorage) return;
|
||||
const t = JSON.parse(localStorage.getItem("token"));
|
||||
if (!t) return;
|
||||
if (!t.idToken || !t.idToken.jwtToken) throw new Error("Invalid token");
|
||||
setToken(t);
|
||||
} catch (e) {
|
||||
document.location = signOut();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -33,21 +41,20 @@ export const UserProvider = ({ children }) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const isError = (resp) => {
|
||||
if (resp === null) return true;
|
||||
if (resp && resp.error) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const startSessionTimer = () => {
|
||||
const duration = 1000 * token.idToken.payload.exp - new Date().getTime();
|
||||
if (!token || !token.idToken || !token.idToken.jwtToken) {
|
||||
throw new Error("No id token");
|
||||
}
|
||||
const payload = parsePayload(token.idToken.jwtToken);
|
||||
if (!payload || !payload.exp) throw new Error("Bad id token payload");
|
||||
const duration = 1000 * payload.exp - new Date().getTime();
|
||||
if (!timer) {
|
||||
timer = getTimerWorker();
|
||||
timer.onMessage(async (e) => {
|
||||
if (e.data === "timeout") {
|
||||
const t = await refreshTokens();
|
||||
if (!isError(t)) return;
|
||||
signOut();
|
||||
if (t && !t.error) return;
|
||||
document.location = signOut();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -61,17 +68,16 @@ export const UserProvider = ({ children }) => {
|
||||
} = token;
|
||||
const result = await auth.verify(idToken);
|
||||
|
||||
if (!result && !result.valid) {
|
||||
if (!result || !result.valid) {
|
||||
const t = await refreshTokens();
|
||||
if (!isError(t)) return;
|
||||
signOut();
|
||||
return;
|
||||
if (!t || t.error) throw new Error("Unable to refresh token");
|
||||
}
|
||||
|
||||
setGroups(getGroups(idToken));
|
||||
startSessionTimer();
|
||||
} catch (e) {
|
||||
signOut();
|
||||
setError(`Verify error. ${e.message}`);
|
||||
document.location = signOut();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -100,6 +106,7 @@ export const UserProvider = ({ children }) => {
|
||||
};
|
||||
|
||||
const signOut = () => {
|
||||
setGroups(null);
|
||||
setToken(null);
|
||||
if (localStorage) {
|
||||
localStorage.removeItem("token");
|
||||
@@ -146,6 +153,7 @@ export const UserProvider = ({ children }) => {
|
||||
value={{
|
||||
fetching,
|
||||
token,
|
||||
groups,
|
||||
error,
|
||||
setError,
|
||||
signIn,
|
||||
|
||||
@@ -11,15 +11,7 @@ import {
|
||||
import { UserProvider, useUserContext } from "../Contexts/UserContext";
|
||||
import auth from "../../services/auth";
|
||||
import getTimerWorker from "../../services/timer";
|
||||
|
||||
const TEST_TOKEN = {
|
||||
idToken: {
|
||||
jwtToken: "TEST",
|
||||
payload: {
|
||||
exp: new Date().getTime() / 1000,
|
||||
},
|
||||
},
|
||||
};
|
||||
import { TEST_AUTH_OBJECT, TEST_EXPECTED_GROUPS } from "../../utils/testing";
|
||||
|
||||
const INVALID_TOKEN_RESPONSE = {
|
||||
error: "Bad Request Error",
|
||||
@@ -36,10 +28,11 @@ const setupSignInEnv = (refreshResponse, valid) => {
|
||||
auth.setVerifyResponse({ valid });
|
||||
};
|
||||
|
||||
const checkBaseResults = (error, fetching, token) => {
|
||||
const checkBaseResults = (error, fetching, token, groups) => {
|
||||
expect(screen.getByTestId("error").innerHTML).toEqual(error);
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual(fetching);
|
||||
expect(screen.getByTestId("token").innerHTML).toEqual(token);
|
||||
expect(screen.getByTestId("groups").innerHTML).toEqual(groups);
|
||||
};
|
||||
|
||||
const checkTokenResults = (timer, token) => {
|
||||
@@ -57,13 +50,14 @@ describe("UseContext", () => {
|
||||
describe("Signin", () => {
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { signIn, error, token, fetching } = useUserContext();
|
||||
const { signIn, error, token, groups, fetching } = useUserContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="error">{error}</div>
|
||||
<div data-testid="fetching">{fetching.toString()}</div>
|
||||
<div data-testid="token">{JSON.stringify(token)}</div>
|
||||
<div data-testid="groups">{groups}</div>
|
||||
<button data-testid="signInNoCode" onClick={() => signIn("")} />
|
||||
<button
|
||||
data-testid="signInInvalidCode"
|
||||
@@ -85,13 +79,13 @@ describe("UseContext", () => {
|
||||
});
|
||||
|
||||
it("Initial state", () => {
|
||||
checkBaseResults("", "false", "null");
|
||||
checkBaseResults("", "false", "null", "");
|
||||
});
|
||||
|
||||
it("No auth code", () => {
|
||||
fireEvent.click(screen.getByTestId("signInNoCode"));
|
||||
|
||||
checkBaseResults("", "false", "null");
|
||||
checkBaseResults("", "false", "null", "");
|
||||
});
|
||||
|
||||
it("Invalid auth code", async () => {
|
||||
@@ -103,14 +97,19 @@ describe("UseContext", () => {
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
|
||||
checkBaseResults("Sign in error. Bad Request Message", "false", "null");
|
||||
checkBaseResults(
|
||||
"Sign in error. Bad Request Message",
|
||||
"false",
|
||||
"null",
|
||||
""
|
||||
);
|
||||
});
|
||||
|
||||
it("Sign in form", async () => {
|
||||
const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
|
||||
const TOKEN_STRING = JSON.stringify(TEST_AUTH_OBJECT);
|
||||
const timer = getTimerWorker();
|
||||
|
||||
setupSignInEnv(TEST_TOKEN, true);
|
||||
setupSignInEnv(TEST_AUTH_OBJECT, true);
|
||||
|
||||
fireEvent.click(screen.getByTestId("signIn"));
|
||||
|
||||
@@ -118,7 +117,7 @@ describe("UseContext", () => {
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
|
||||
checkBaseResults("", "false", TOKEN_STRING);
|
||||
checkBaseResults("", "false", TOKEN_STRING, TEST_EXPECTED_GROUPS);
|
||||
checkTokenResults(timer, TOKEN_STRING);
|
||||
});
|
||||
});
|
||||
@@ -126,12 +125,20 @@ describe("UseContext", () => {
|
||||
describe("Signout", () => {
|
||||
beforeEach(async () => {
|
||||
const TestComp = () => {
|
||||
const { signIn, signOut, error, token, fetching } = useUserContext();
|
||||
const {
|
||||
signIn,
|
||||
signOut,
|
||||
error,
|
||||
token,
|
||||
groups,
|
||||
fetching,
|
||||
} = useUserContext();
|
||||
return (
|
||||
<>
|
||||
<div data-testid="error">{error}</div>
|
||||
<div data-testid="fetching">{fetching.toString()}</div>
|
||||
<div data-testid="token">{JSON.stringify(token)}</div>
|
||||
<div data-testid="groups">{groups}</div>
|
||||
<button data-testid="signIn" onClick={() => signIn("TEST_CODE")} />
|
||||
<button data-testid="signOut" onClick={() => signOut()} />
|
||||
</>
|
||||
@@ -142,7 +149,7 @@ describe("UseContext", () => {
|
||||
<TestComp />
|
||||
</UserProvider>
|
||||
);
|
||||
auth.setSignInResponse(TEST_TOKEN);
|
||||
auth.setSignInResponse(TEST_AUTH_OBJECT);
|
||||
fireEvent.click(screen.getByTestId("signIn"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
@@ -154,10 +161,9 @@ describe("UseContext", () => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("Token cleared", () => {
|
||||
it("Token cleared", async () => {
|
||||
fireEvent.click(screen.getByTestId("signOut"));
|
||||
|
||||
checkBaseResults("", "false", "null");
|
||||
checkBaseResults("", "false", "null", "");
|
||||
if (!localStorage) return;
|
||||
expect(localStorage.getItem("token")).toBeNull();
|
||||
});
|
||||
@@ -166,13 +172,14 @@ describe("UseContext", () => {
|
||||
describe("Refresh", () => {
|
||||
beforeEach(() => {
|
||||
const TestComp = () => {
|
||||
const { refresh, error, token, fetching } = useUserContext();
|
||||
const { refresh, error, token, groups, fetching } = useUserContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="error">{error}</div>
|
||||
<div data-testid="fetching">{fetching.toString()}</div>
|
||||
<div data-testid="token">{JSON.stringify(token)}</div>
|
||||
<div data-testid="groups">{groups}</div>
|
||||
<button data-testid="refreshNoToken" onClick={() => refresh("")} />
|
||||
<button
|
||||
data-testid="refreshInvalidToken"
|
||||
@@ -197,12 +204,12 @@ describe("UseContext", () => {
|
||||
});
|
||||
|
||||
it("Initial state", () => {
|
||||
checkBaseResults("", "false", "null");
|
||||
checkBaseResults("", "false", "null", "");
|
||||
});
|
||||
|
||||
it("No refresh token", () => {
|
||||
fireEvent.click(screen.getByTestId("refreshNoToken"));
|
||||
checkBaseResults("Refresh error. Token required", "false", "null");
|
||||
checkBaseResults("Refresh error. Token required", "false", "null", "");
|
||||
});
|
||||
|
||||
it("Invalid refresh token", async () => {
|
||||
@@ -213,21 +220,26 @@ describe("UseContext", () => {
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
|
||||
checkBaseResults("Refresh error. Bad Request Message", "false", "null");
|
||||
checkBaseResults(
|
||||
"Refresh error. Bad Request Message",
|
||||
"false",
|
||||
"null",
|
||||
""
|
||||
);
|
||||
});
|
||||
|
||||
it("Valid refresh token", async () => {
|
||||
const TOKEN_STRING = JSON.stringify(TEST_TOKEN);
|
||||
const TOKEN_STRING = JSON.stringify(TEST_AUTH_OBJECT);
|
||||
const timer = getTimerWorker();
|
||||
|
||||
setupRefreshEnv(TEST_TOKEN, true);
|
||||
setupRefreshEnv(TEST_AUTH_OBJECT, true);
|
||||
|
||||
fireEvent.click(screen.getByTestId("refreshValidToken"));
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId("fetching").innerHTML).toEqual("true")
|
||||
);
|
||||
|
||||
checkBaseResults("", "false", TOKEN_STRING);
|
||||
checkBaseResults("", "false", TOKEN_STRING, TEST_EXPECTED_GROUPS);
|
||||
checkTokenResults(timer, TOKEN_STRING);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,11 +15,20 @@ const validateAdd = (vehicle) => {
|
||||
if (vehicle.vin.length > 17) {
|
||||
throw new Error("VIN cannot be larger than 17 characters");
|
||||
}
|
||||
|
||||
if (!vehicle.model || vehicle.model.length === 0) {
|
||||
throw new Error("model required");
|
||||
}
|
||||
|
||||
if (!vehicle.year || vehicle.year < 2000 || vehicle.year > 9999) {
|
||||
throw new Error("year required");
|
||||
}
|
||||
};
|
||||
|
||||
export const VehicleProvider = ({ children }) => {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [vehicles, setVehicles] = useState([]);
|
||||
const [totalVehicles, setTotalVehicles] = useState(0);
|
||||
|
||||
const getVehicles = async (search, token) => {
|
||||
try {
|
||||
@@ -30,6 +39,9 @@ export const VehicleProvider = ({ children }) => {
|
||||
throw new Error(`Get vehicles error. ${result.message}`);
|
||||
} else {
|
||||
setVehicles(result.data);
|
||||
if (result.total) {
|
||||
setTotalVehicles(result.total);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setBusy(false);
|
||||
@@ -53,6 +65,7 @@ export const VehicleProvider = ({ children }) => {
|
||||
value={{
|
||||
busy,
|
||||
vehicles,
|
||||
totalVehicles,
|
||||
getVehicles,
|
||||
addVehicle,
|
||||
}}
|
||||
|
||||
@@ -83,7 +83,9 @@ describe("VehicleContext", () => {
|
||||
<button data-testid="addVehiclesNoVIN" onClick={() => add({})} />
|
||||
<button
|
||||
data-testid="addVehicles"
|
||||
onClick={() => add({ vin: "XXXXXXXXXXX" })}
|
||||
onClick={() =>
|
||||
add({ vin: "XXXXXXXXXXX", model: "Ocean", year: 3000 })
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
import { getGroups } from "../../../utils/roles";
|
||||
|
||||
let token = null;
|
||||
let groups = null;
|
||||
let fetching = false;
|
||||
let error = null;
|
||||
let signInResp = {};
|
||||
let authorizeURL = "https://cognito.com/authorize?redirect=https://example.com/callback";
|
||||
let logoutURL = "https://cognito.com/logout?redirect=https://example.com/callback";
|
||||
let authorizeURL =
|
||||
"https://cognito.com/authorize?redirect=https://example.com/callback";
|
||||
let logoutURL =
|
||||
"https://cognito.com/logout?redirect=https://example.com/callback";
|
||||
|
||||
export const UserProvider = ({ children }) => {
|
||||
return <div data-testid="mocked-userprovider">{children}</div>;
|
||||
@@ -15,6 +20,7 @@ export const useUserContext = () => ({
|
||||
token,
|
||||
fetching,
|
||||
error,
|
||||
groups,
|
||||
signIn: jest.fn(() => signInResp),
|
||||
signOut: jest.fn(),
|
||||
getAuthorizeURL: jest.fn(() => authorizeURL),
|
||||
@@ -26,6 +32,11 @@ export const useUserContext = () => ({
|
||||
|
||||
export const setToken = (val) => {
|
||||
token = val;
|
||||
if (!val || !val.idToken || !val.idToken.jwtToken) {
|
||||
groups = null;
|
||||
} else {
|
||||
groups = getGroups(val.idToken.jwtToken);
|
||||
}
|
||||
};
|
||||
|
||||
export const setFetching = (val) => {
|
||||
|
||||
Reference in New Issue
Block a user