Compare commits
79 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a68d2791d | ||
|
|
3691d32aaa | ||
|
|
7e741e4af9 | ||
|
|
f556d59ad7 | ||
|
|
7843ca9b1a | ||
|
|
c8701aba63 | ||
|
|
f6e65f82e5 | ||
|
|
8b7bb099f3 | ||
|
|
663d91b648 | ||
|
|
2a7686ec75 | ||
|
|
549cb56cdf | ||
|
|
146bb6c5c0 | ||
|
|
67b6da7c31 | ||
|
|
624894621b | ||
|
|
3fba215266 | ||
|
|
bbf291e8f3 | ||
|
|
341fc09c22 | ||
|
|
fb30529808 | ||
|
|
46999145fc | ||
|
|
9d1f810af2 | ||
|
|
b9693aae95 | ||
|
|
02f5f1985c | ||
|
|
37edceee84 | ||
|
|
1fd63012b0 | ||
|
|
cf084fa168 | ||
|
|
96af289640 | ||
|
|
f8df351de6 | ||
|
|
b8c096f4ff | ||
|
|
93ad23b615 | ||
|
|
0a918535bb | ||
|
|
4863b16b5f | ||
|
|
a27f5e2153 | ||
|
|
3ffea50072 | ||
|
|
34af7d3880 | ||
|
|
4432721c27 | ||
|
|
65ad4d9426 | ||
|
|
18924b4f08 | ||
|
|
c524f5f0e0 | ||
|
|
2fbac78eec | ||
|
|
4734971d48 | ||
|
|
fc1055c644 | ||
|
|
24798390b5 | ||
|
|
a992dead04 | ||
|
|
f039cd8d0d | ||
|
|
494e691230 | ||
|
|
4cc7366290 | ||
|
|
fd9d660a61 | ||
|
|
0b5bd4f718 | ||
|
|
7511b42bd4 | ||
|
|
26f94c4d5b | ||
|
|
aa55f4840b | ||
|
|
2810428d19 | ||
|
|
83da64f96b | ||
|
|
1f8d027f97 | ||
|
|
2f8ba20a5b | ||
|
|
b525f920e0 | ||
|
|
91fe6745fe | ||
|
|
b9073fe3f5 | ||
|
|
2c95b49ae1 | ||
|
|
9bde06e110 | ||
|
|
30449ca113 | ||
|
|
0405d19f98 | ||
|
|
2c5310403b | ||
|
|
e2ef4f1caf | ||
|
|
b32a2d4d86 | ||
|
|
7c92484ae0 | ||
|
|
67cada5d8e | ||
|
|
4bf68b637f | ||
|
|
b40c433865 | ||
|
|
55ed499ab5 | ||
|
|
353bc3bc05 | ||
|
|
df5c96345c | ||
|
|
2c7c187c45 | ||
|
|
3a5a78d60a | ||
|
|
6dd2871c07 | ||
|
|
ef36ab9da0 | ||
|
|
a917f6bcdf | ||
|
|
c5d6457146 | ||
|
|
f3831e934f |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -121,3 +121,10 @@ logs/oAuthClient-log.log
|
|||||||
/*.env.*
|
/*.env.*
|
||||||
.idea/*
|
.idea/*
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# Vitest
|
||||||
|
vitest-report*/
|
||||||
|
vitest-coverage/
|
||||||
|
*.vitest.log
|
||||||
|
test-output.txt
|
||||||
|
|
||||||
|
|||||||
@@ -12,3 +12,5 @@ VITE_APP_AXIOS_BASE_API_URL=/api/
|
|||||||
VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
|
TEST_USERNAME="test@imex.dev"
|
||||||
|
TEST_PASSWORD="test123"
|
||||||
|
|||||||
@@ -14,3 +14,5 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
|||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_COUNTRY=USA
|
VITE_APP_COUNTRY=USA
|
||||||
VITE_APP_INSTANCE=ROME
|
VITE_APP_INSTANCE=ROME
|
||||||
|
TEST_USERNAME="test@imex.dev"
|
||||||
|
TEST_PASSWORD="test123"
|
||||||
|
|||||||
11
client/.gitignore
vendored
11
client/.gitignore
vendored
@@ -1,3 +1,14 @@
|
|||||||
|
# Vitest
|
||||||
|
vitest-report*/
|
||||||
|
vitest-coverage/
|
||||||
|
*.vitest.log
|
||||||
|
test-output.txt
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
playwright-report/
|
||||||
|
test-results/
|
||||||
|
playwright/.cache/
|
||||||
|
*.playwright.log
|
||||||
|
|
||||||
# Sentry Config File
|
# Sentry Config File
|
||||||
.sentryclirc
|
.sentryclirc
|
||||||
|
|||||||
2490
client/package-lock.json
generated
2490
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,6 @@
|
|||||||
"@sentry/vite-plugin": "^3.2.2",
|
"@sentry/vite-plugin": "^3.2.2",
|
||||||
"@splitsoftware/splitio-react": "^2.0.1",
|
"@splitsoftware/splitio-react": "^2.0.1",
|
||||||
"@tanem/react-nprogress": "^5.0.53",
|
"@tanem/react-nprogress": "^5.0.53",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
|
||||||
"antd": "^5.24.5",
|
"antd": "^5.24.5",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^4.2.0",
|
"apollo-link-sentry": "^4.2.0",
|
||||||
@@ -100,7 +99,14 @@
|
|||||||
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
|
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
|
||||||
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
|
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
|
||||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||||
"eulaize": "node src/utils/eulaize.js"
|
"eulaize": "node src/utils/eulaize.js",
|
||||||
|
"test:unit": "vitest run",
|
||||||
|
"test:watch": "vitest",
|
||||||
|
"test:e2e:imex": "playwright test --config playwright.config.js",
|
||||||
|
"test:e2e:rome": "playwright test --config playwright.rome.config.js",
|
||||||
|
"test:e2e:imex:headed": "playwright test --config playwright.config.js --headed",
|
||||||
|
"test:e2e:rome:headed": "playwright test --config playwright.rome.config.js --headed",
|
||||||
|
"test:e2e:report": "playwright show-report"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@@ -128,7 +134,12 @@
|
|||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@eslint/js": "^9.23.0",
|
"@eslint/js": "^9.23.0",
|
||||||
|
"@playwright/test": "^1.51.1",
|
||||||
"@sentry/webpack-plugin": "^3.2.2",
|
"@sentry/webpack-plugin": "^3.2.2",
|
||||||
|
"@testing-library/dom": "^10.4.0",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@testing-library/react": "^16.2.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"browserslist": "^4.24.4",
|
"browserslist": "^4.24.4",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
@@ -136,8 +147,10 @@
|
|||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"eslint-plugin-react": "^7.37.4",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
|
"jsdom": "^26.0.0",
|
||||||
"memfs": "^4.17.0",
|
"memfs": "^4.17.0",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
|
"playwright": "^1.51.1",
|
||||||
"react-error-overlay": "^6.1.0",
|
"react-error-overlay": "^6.1.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
@@ -147,6 +160,7 @@
|
|||||||
"vite-plugin-node-polyfills": "^0.23.0",
|
"vite-plugin-node-polyfills": "^0.23.0",
|
||||||
"vite-plugin-pwa": "^0.21.2",
|
"vite-plugin-pwa": "^0.21.2",
|
||||||
"vite-plugin-style-import": "^2.0.0",
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
|
"vitest": "^3.0.9",
|
||||||
"workbox-window": "^7.3.0"
|
"workbox-window": "^7.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
client/playwright.config.js
Normal file
25
client/playwright.config.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineConfig } from "@playwright/test";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: "./.env.development.imex",
|
||||||
|
prefix: "TEST_"
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests/e2e",
|
||||||
|
testMatch: "*.e2e.js",
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
reporter: [["list"], ["html"]],
|
||||||
|
use: {
|
||||||
|
baseURL: "https://localhost:3000",
|
||||||
|
browser: "chromium",
|
||||||
|
ignoreHTTPSErrors: true
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
command: "npm run start:imex",
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||||
|
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||||
|
}
|
||||||
|
});
|
||||||
25
client/playwright.rome.config.js
Normal file
25
client/playwright.rome.config.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { defineConfig } from "@playwright/test";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
dotenv.config({
|
||||||
|
path: "./.env.development.rome",
|
||||||
|
prefix: "TEST_"
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests/e2e",
|
||||||
|
testMatch: "*.e2e.js",
|
||||||
|
timeout: 60 * 1000,
|
||||||
|
reporter: [["list"], ["html"]],
|
||||||
|
use: {
|
||||||
|
baseURL: "https://localhost:3000",
|
||||||
|
browser: "chromium",
|
||||||
|
ignoreHTTPSErrors: true
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
command: "npm run start:rome",
|
||||||
|
ignoreHTTPSErrors: true,
|
||||||
|
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||||
|
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -10,6 +10,7 @@ import client from "../utils/GraphQLClient";
|
|||||||
import App from "./App";
|
import App from "./App";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import themeProvider from "./themeProvider";
|
import themeProvider from "./themeProvider";
|
||||||
|
import { CookiesProvider } from "react-cookie";
|
||||||
|
|
||||||
// Base Split configuration
|
// Base Split configuration
|
||||||
const config = {
|
const config = {
|
||||||
@@ -38,26 +39,28 @@ function AppContainer() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ApolloProvider client={client}>
|
<CookiesProvider>
|
||||||
<ConfigProvider
|
<ApolloProvider client={client}>
|
||||||
input={{ autoComplete: "new-password" }}
|
<ConfigProvider
|
||||||
locale={enLocale}
|
input={{ autoComplete: "new-password" }}
|
||||||
theme={themeProvider}
|
locale={enLocale}
|
||||||
form={{
|
theme={themeProvider}
|
||||||
validateMessages: {
|
form={{
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
validateMessages: {
|
||||||
required: t("general.validation.required", { label: "${label}" })
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
}
|
required: t("general.validation.required", { label: "${label}" })
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
<GlobalLoadingBar />
|
>
|
||||||
<SplitFactoryProvider config={config}>
|
<GlobalLoadingBar />
|
||||||
<SplitClientProvider>
|
<SplitFactoryProvider config={config}>
|
||||||
<App />
|
<SplitClientProvider>
|
||||||
</SplitClientProvider>
|
<App />
|
||||||
</SplitFactoryProvider>
|
</SplitClientProvider>
|
||||||
</ConfigProvider>
|
</SplitFactoryProvider>
|
||||||
</ApolloProvider>
|
</ConfigProvider>
|
||||||
|
</ApolloProvider>
|
||||||
|
</CookiesProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`Alert component should render Alert component 1`] = `ShallowWrapper {}`;
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { shallow } from "enzyme";
|
|
||||||
import React from "react";
|
|
||||||
import Alert from "./alert.component";
|
|
||||||
|
|
||||||
describe("Alert component", () => {
|
|
||||||
let wrapper;
|
|
||||||
beforeEach(() => {
|
|
||||||
const mockProps = {
|
|
||||||
type: "error",
|
|
||||||
message: "Test error message."
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper = shallow(<Alert {...mockProps} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render Alert component", () => {
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
31
client/src/components/alert/alert.component.test.jsx
Normal file
31
client/src/components/alert/alert.component.test.jsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import AlertComponent from "./alert.component";
|
||||||
|
|
||||||
|
describe("AlertComponent", () => {
|
||||||
|
it("renders with default props", () => {
|
||||||
|
render(<AlertComponent message="Default Alert" />);
|
||||||
|
expect(screen.getByText("Default Alert")).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("alert")).toHaveClass("ant-alert");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies type prop correctly", () => {
|
||||||
|
render(<AlertComponent message="Success Alert" type="success" />);
|
||||||
|
const alert = screen.getByRole("alert");
|
||||||
|
expect(screen.getByText("Success Alert")).toBeInTheDocument();
|
||||||
|
expect(alert).toHaveClass("ant-alert-success");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("displays description when provided", () => {
|
||||||
|
render(<AlertComponent message="Error Alert" description="Something went wrong" type="error" />);
|
||||||
|
expect(screen.getByText("Error Alert")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("alert")).toHaveClass("ant-alert-error");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is closable and shows icon when props are set", () => {
|
||||||
|
render(<AlertComponent message="Warning Alert" type="warning" showIcon closable />);
|
||||||
|
expect(screen.getByText("Warning Alert")).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button", { name: /close/i })).toBeInTheDocument(); // Close button
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AllocationsAssignmentComponent component should create an allocation on save 1`] = `ReactWrapper {}`;
|
|
||||||
|
|
||||||
exports[`AllocationsAssignmentComponent component should render AllocationsAssignmentComponent component 1`] = `ReactWrapper {}`;
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { mount } from "enzyme";
|
|
||||||
import React from "react";
|
|
||||||
import { MockBodyshop } from "../../utils/TestingHelpers";
|
|
||||||
import { AllocationsAssignmentComponent } from "./allocations-assignment.component";
|
|
||||||
|
|
||||||
describe("AllocationsAssignmentComponent component", () => {
|
|
||||||
let wrapper;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const mockProps = {
|
|
||||||
bodyshop: MockBodyshop,
|
|
||||||
handleAssignment: jest.fn(),
|
|
||||||
assignment: {},
|
|
||||||
setAssignment: jest.fn(),
|
|
||||||
visibilityState: [false, jest.fn()],
|
|
||||||
maxHours: 4
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapper = mount(<AllocationsAssignmentComponent {...mockProps} />);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render AllocationsAssignmentComponent component", () => {
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render a list of employees", () => {
|
|
||||||
const empList = wrapper.find("#employeeSelector");
|
|
||||||
expect(empList.children()).to.have.lengthOf(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should create an allocation on save", () => {
|
|
||||||
wrapper.find("Button").simulate("click");
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -20,6 +20,7 @@ function FeatureWrapper({
|
|||||||
children,
|
children,
|
||||||
upsellComponent,
|
upsellComponent,
|
||||||
bypass,
|
bypass,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
...restProps
|
...restProps
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -78,7 +79,11 @@ export function HasFeatureAccess({ featureName, bodyshop, bypass, debug = false
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return bodyshop?.features?.allAccess || dayjs(bodyshop?.features[featureName]).isAfter(dayjs());
|
return (
|
||||||
|
bodyshop?.features?.allAccess ||
|
||||||
|
bodyshop?.features?.[featureName] ||
|
||||||
|
dayjs(bodyshop?.features[featureName]).isAfter(dayjs())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(FeatureWrapper);
|
export default connect(mapStateToProps, null)(FeatureWrapper);
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
|||||||
args: [],
|
args: [],
|
||||||
imex: () => {
|
imex: () => {
|
||||||
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||||
|
window.$crisp.push(["set", "session:segments", [[`region:${payload.region_config}`]]]);
|
||||||
if (authRecord[0] && authRecord[0].user.validemail) {
|
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||||
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import { configure } from "enzyme";
|
|
||||||
import Adapter from "enzyme-adapter-react-16";
|
|
||||||
|
|
||||||
configure({ adapter: new Adapter() });
|
|
||||||
@@ -3679,7 +3679,8 @@
|
|||||||
"signinerror": {
|
"signinerror": {
|
||||||
"auth/user-disabled": "User account disabled. ",
|
"auth/user-disabled": "User account disabled. ",
|
||||||
"auth/user-not-found": "A user with this email does not exist.",
|
"auth/user-not-found": "A user with this email does not exist.",
|
||||||
"auth/wrong-password": "The email and password combination you provided is incorrect."
|
"auth/wrong-password": "The email and password combination you provided is incorrect.",
|
||||||
|
"auth/invalid-email": "A user with this email does not exist."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3679,7 +3679,8 @@
|
|||||||
"signinerror": {
|
"signinerror": {
|
||||||
"auth/user-disabled": "",
|
"auth/user-disabled": "",
|
||||||
"auth/user-not-found": "",
|
"auth/user-not-found": "",
|
||||||
"auth/wrong-password": ""
|
"auth/wrong-password": "",
|
||||||
|
"auth/invalid-email": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3679,7 +3679,8 @@
|
|||||||
"signinerror": {
|
"signinerror": {
|
||||||
"auth/user-disabled": "",
|
"auth/user-disabled": "",
|
||||||
"auth/user-not-found": "",
|
"auth/user-not-found": "",
|
||||||
"auth/wrong-password": ""
|
"auth/wrong-password": "",
|
||||||
|
"auth/invalid-email": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
export const MockBodyshop = {
|
|
||||||
address1: "123 Fake St",
|
|
||||||
address2: "Unit #100",
|
|
||||||
city: "Vancouver",
|
|
||||||
country: "Canada",
|
|
||||||
created_at: "2019-12-10T20:03:06.420853+00:00",
|
|
||||||
email: "snaptsoft@gmail.com",
|
|
||||||
federal_tax_id: "GST10150492",
|
|
||||||
id: "52b7357c-0edd-4c95-85c3-dfdbcdfad9ac",
|
|
||||||
insurance_vendor_id: "F123456",
|
|
||||||
logo_img_path: "https://www.snapt.ca/assets/logo-placeholder.png",
|
|
||||||
md_ro_statuses: {
|
|
||||||
statuses: [
|
|
||||||
"Open",
|
|
||||||
"Scheduled",
|
|
||||||
"Arrived",
|
|
||||||
"Repair Plan",
|
|
||||||
"Parts",
|
|
||||||
"Body",
|
|
||||||
"Prep",
|
|
||||||
"Paint",
|
|
||||||
"Reassembly",
|
|
||||||
"Sublet",
|
|
||||||
"Detail",
|
|
||||||
"Completed",
|
|
||||||
"Delivered",
|
|
||||||
"Invoiced",
|
|
||||||
"Exported"
|
|
||||||
],
|
|
||||||
open_statuses: ["Open", "Scheduled", "Arrived", "Repair Plan", "Parts", "Body", "Prep", "Paint"],
|
|
||||||
default_arrived: "Arrived",
|
|
||||||
default_exported: "Exported",
|
|
||||||
default_imported: "Open",
|
|
||||||
default_invoiced: "Invoiced",
|
|
||||||
default_completed: "Completed",
|
|
||||||
default_delivered: "Delivered",
|
|
||||||
default_scheduled: "Scheduled"
|
|
||||||
},
|
|
||||||
md_order_statuses: {
|
|
||||||
statuses: ["Ordered", "Received", "Canceled", "Backordered"],
|
|
||||||
default_bo: "Backordered",
|
|
||||||
default_ordered: "Ordered",
|
|
||||||
default_canceled: "Canceled",
|
|
||||||
default_received: "Received"
|
|
||||||
},
|
|
||||||
shopname: "Testing Collision",
|
|
||||||
state: "BC",
|
|
||||||
state_tax_id: "PST1000-2991",
|
|
||||||
updated_at: "2020-03-23T22:06:03.509544+00:00",
|
|
||||||
zip_post: "V6B 1M9",
|
|
||||||
region_config: "CA_BC",
|
|
||||||
md_responsibility_centers: {
|
|
||||||
costs: [
|
|
||||||
"Aftermarket",
|
|
||||||
"ATS",
|
|
||||||
"Body",
|
|
||||||
"Detail",
|
|
||||||
"Daignostic",
|
|
||||||
"Electrical",
|
|
||||||
"Chrome",
|
|
||||||
"Frame",
|
|
||||||
"Mechanical",
|
|
||||||
"Refinish",
|
|
||||||
"Structural",
|
|
||||||
"Existing",
|
|
||||||
"Glass",
|
|
||||||
"LKQ",
|
|
||||||
"OEM",
|
|
||||||
"OEM Partial",
|
|
||||||
"Re-cored",
|
|
||||||
"Remanufactured",
|
|
||||||
"Other",
|
|
||||||
"Sublet",
|
|
||||||
"Towing"
|
|
||||||
],
|
|
||||||
profits: [
|
|
||||||
"Aftermarket",
|
|
||||||
"ATS",
|
|
||||||
"Body",
|
|
||||||
"Detail",
|
|
||||||
"Daignostic",
|
|
||||||
"Electrical",
|
|
||||||
"Chrome",
|
|
||||||
"Frame",
|
|
||||||
"Mechanical",
|
|
||||||
"Refinish",
|
|
||||||
"Structural",
|
|
||||||
"Existing",
|
|
||||||
"Glass",
|
|
||||||
"LKQ",
|
|
||||||
"OEM",
|
|
||||||
"OEM Partial",
|
|
||||||
"Re-cored",
|
|
||||||
"Remanufactured",
|
|
||||||
"Other",
|
|
||||||
"Sublet",
|
|
||||||
"Towing"
|
|
||||||
],
|
|
||||||
defaults: {
|
|
||||||
ATS: "ATS",
|
|
||||||
LAB: "Body",
|
|
||||||
LAD: "Diagnostic",
|
|
||||||
LAE: "Electrical",
|
|
||||||
LAF: "Frame",
|
|
||||||
LAG: "Glass",
|
|
||||||
LAM: "Mechanical",
|
|
||||||
LAR: "Refinish",
|
|
||||||
LAS: "Structural",
|
|
||||||
LAU: "Detail",
|
|
||||||
PAA: "Aftermarket",
|
|
||||||
PAC: "Chrome",
|
|
||||||
PAL: "LKQ",
|
|
||||||
PAM: "Remanufactured",
|
|
||||||
PAN: "OEM",
|
|
||||||
PAO: "Other",
|
|
||||||
PAP: "OEM Partial",
|
|
||||||
PAR: "16",
|
|
||||||
TOW: "Towing"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
employees: [
|
|
||||||
{
|
|
||||||
id: "075b744c-8919-49ca-abb2-ccd51040326d",
|
|
||||||
first_name: "Patrick",
|
|
||||||
last_name: "BODY123",
|
|
||||||
employee_number: "101",
|
|
||||||
cost_center: "Body",
|
|
||||||
__typename: "employees"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "8cc787d3-1cfe-49d3-8a15-8469cd5c2e41",
|
|
||||||
first_name: "Patrick",
|
|
||||||
last_name: "Painter",
|
|
||||||
employee_number: "10211",
|
|
||||||
cost_center: "REFINISH",
|
|
||||||
__typename: "employees"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
14
client/tests/Button.test.jsx
Normal file
14
client/tests/Button.test.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Button.test.jsx
|
||||||
|
import React from "react";
|
||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import "antd/dist/reset.css"; // Optional: include if needed for styling reset
|
||||||
|
|
||||||
|
describe("AntD Button", () => {
|
||||||
|
it("renders with correct text", () => {
|
||||||
|
render(<Button>Click me</Button>);
|
||||||
|
const button = screen.getByRole("button", { name: /click me/i });
|
||||||
|
expect(button).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
6
client/tests/e2e/homepage.e2e.js
Normal file
6
client/tests/e2e/homepage.e2e.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
|
||||||
|
test("homepage loads correctly", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await expect(page.locator("h1")).toContainText("ImEX Online");
|
||||||
|
});
|
||||||
28
client/tests/e2e/signin.e2e.js
Normal file
28
client/tests/e2e/signin.e2e.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { test, expect } from "@playwright/test";
|
||||||
|
import { login } from "./utils/login";
|
||||||
|
|
||||||
|
test.describe("SignInComponent", () => {
|
||||||
|
test("successfully logs in with valid credentials", async ({ page }) => {
|
||||||
|
const email = process.env.TEST_USERNAME;
|
||||||
|
const password = process.env.TEST_PASSWORD;
|
||||||
|
|
||||||
|
await login(page, { email, password });
|
||||||
|
|
||||||
|
// Additional assertions after login (optional)
|
||||||
|
await expect(page).toHaveURL(/\/manage\//);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("displays error on invalid credentials", async ({ page }) => {
|
||||||
|
await page.goto("/"); // Adjust if login route differs
|
||||||
|
|
||||||
|
// Fill form with invalid credentials
|
||||||
|
await page.fill('input[placeholder="Username"]', "wronguser@example.com");
|
||||||
|
await page.fill('input[placeholder="Password"]', "wrongpassword");
|
||||||
|
await page.click("button.login-btn");
|
||||||
|
|
||||||
|
// Check for error alert
|
||||||
|
const alert = page.locator(".ant-alert-error");
|
||||||
|
await expect(alert).toBeVisible();
|
||||||
|
await expect(alert).toContainText("A user with this email does not exist.");
|
||||||
|
});
|
||||||
|
});
|
||||||
21
client/tests/e2e/utils/login.js
Normal file
21
client/tests/e2e/utils/login.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { expect } from "@playwright/test";
|
||||||
|
|
||||||
|
export async function login(page, { email, password }) {
|
||||||
|
// Navigate to the login page
|
||||||
|
await page.goto("/"); // Adjust if your login route differs (e.g., '/login')
|
||||||
|
|
||||||
|
// Fill email field
|
||||||
|
await page.fill('input[placeholder="Username"]', email); // Matches Ant Design Input placeholder
|
||||||
|
|
||||||
|
// Fill password field
|
||||||
|
await page.fill('input[placeholder="Password"]', password);
|
||||||
|
|
||||||
|
// Click login button
|
||||||
|
await page.click("button.login-btn");
|
||||||
|
|
||||||
|
// Wait for navigation or success indicator (e.g., redirect to /manage/)
|
||||||
|
await page.waitForURL(/\/manage\//, { timeout: 10000 }); // Adjust based on redirect
|
||||||
|
|
||||||
|
// Verify successful login (e.g., check for a dashboard element)
|
||||||
|
await expect(page.locator("text=Manage")).toBeVisible(); // Adjust to your app’s post-login UI
|
||||||
|
}
|
||||||
5
client/tests/setup.js
Normal file
5
client/tests/setup.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { afterEach } from "vitest";
|
||||||
|
import { cleanup } from "@testing-library/react";
|
||||||
|
import "@testing-library/jest-dom";
|
||||||
|
|
||||||
|
afterEach(() => cleanup());
|
||||||
27
client/tests/setupI18n.js
Normal file
27
client/tests/setupI18n.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import i18n from "i18next";
|
||||||
|
import { initReactI18next } from "react-i18next";
|
||||||
|
import en_Translation from "../src/translations/en_us/common.json";
|
||||||
|
import es_Translation from "../src/translations/es/common.json";
|
||||||
|
import fr_Translation from "../src/translations/fr/common.json";
|
||||||
|
|
||||||
|
const resources = {
|
||||||
|
"en-US": en_Translation,
|
||||||
|
"fr-CA": fr_Translation,
|
||||||
|
"es-MX": es_Translation
|
||||||
|
};
|
||||||
|
|
||||||
|
i18n.use(initReactI18next).init({
|
||||||
|
resources,
|
||||||
|
lng: "en-US", // Default to en-US for tests (no LanguageDetector)
|
||||||
|
fallbackLng: "en-US",
|
||||||
|
debug: false, // Disable debug in tests
|
||||||
|
react: {
|
||||||
|
useSuspense: false // Disable Suspense for Vitest
|
||||||
|
},
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false, // React handles XSS
|
||||||
|
skipOnVariables: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
@@ -191,11 +191,13 @@ export default defineConfig({
|
|||||||
"@sentry/react": ["@sentry/react"],
|
"@sentry/react": ["@sentry/react"],
|
||||||
"@splitsoftware/splitio-react": ["@splitsoftware/splitio-react"],
|
"@splitsoftware/splitio-react": ["@splitsoftware/splitio-react"],
|
||||||
logrocket: ["logrocket"],
|
logrocket: ["logrocket"],
|
||||||
"@firebase/analytics": ["@firebase/analytics"],
|
firebase: [
|
||||||
"@firebase/app": ["@firebase/app"],
|
"@firebase/analytics",
|
||||||
"@firebase/firestore": ["@firebase/firestore"],
|
"@firebase/app",
|
||||||
"@firebase/auth": ["@firebase/auth"],
|
"@firebase/firestore",
|
||||||
"@firebase/messaging": ["@firebase/messaging"],
|
"@firebase/auth",
|
||||||
|
"@firebase/messaging"
|
||||||
|
],
|
||||||
markerjs2: ["markerjs2"],
|
markerjs2: ["markerjs2"],
|
||||||
"@apollo/client": ["@apollo/client"],
|
"@apollo/client": ["@apollo/client"],
|
||||||
"libphonenumber-js": ["libphonenumber-js"]
|
"libphonenumber-js": ["libphonenumber-js"]
|
||||||
@@ -218,7 +220,13 @@ export default defineConfig({
|
|||||||
"react-router-dom",
|
"react-router-dom",
|
||||||
"dayjs",
|
"dayjs",
|
||||||
"redux",
|
"redux",
|
||||||
"react-redux"
|
"react-redux",
|
||||||
|
"@firebase/app",
|
||||||
|
"@firebase/analytics",
|
||||||
|
"@firebase/firestore",
|
||||||
|
"@firebase/auth",
|
||||||
|
"@firebase/messaging",
|
||||||
|
"@firebase/util"
|
||||||
],
|
],
|
||||||
esbuildOptions: {
|
esbuildOptions: {
|
||||||
// Update for Vite 6: Use proper file extensions
|
// Update for Vite 6: Use proper file extensions
|
||||||
|
|||||||
19
client/vitest.config.js
Normal file
19
client/vitest.config.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
environment: "jsdom",
|
||||||
|
globals: true,
|
||||||
|
setupFiles: "./tests/setup.js",
|
||||||
|
testTimeout: 10000 // 10 seconds (in milliseconds)
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
silenceDeprecations: ["import"] // Suppress @import warnings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
1609
package-lock.json
generated
1609
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -10,7 +10,9 @@
|
|||||||
"setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i",
|
"setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i",
|
||||||
"setup:win": "rimraf node_modules && npm i && cd client && rimraf node_modules && npm i",
|
"setup:win": "rimraf node_modules && npm i && cd client && rimraf node_modules && npm i",
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
|
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"",
|
||||||
|
"test:unit": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-cloudwatch-logs": "^3.772.0",
|
"@aws-sdk/client-cloudwatch-logs": "^3.772.0",
|
||||||
@@ -19,8 +21,8 @@
|
|||||||
"@aws-sdk/client-secrets-manager": "^3.772.0",
|
"@aws-sdk/client-secrets-manager": "^3.772.0",
|
||||||
"@aws-sdk/client-ses": "^3.772.0",
|
"@aws-sdk/client-ses": "^3.772.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.772.0",
|
"@aws-sdk/credential-provider-node": "^3.772.0",
|
||||||
"@aws-sdk/lib-storage": "^3.743.0",
|
"@aws-sdk/lib-storage": "^3.774.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.731.1",
|
"@aws-sdk/s3-request-presigner": "^3.774.0",
|
||||||
"@opensearch-project/opensearch": "^2.13.0",
|
"@opensearch-project/opensearch": "^2.13.0",
|
||||||
"@socket.io/admin-ui": "^0.5.1",
|
"@socket.io/admin-ui": "^0.5.1",
|
||||||
"@socket.io/redis-adapter": "^8.3.0",
|
"@socket.io/redis-adapter": "^8.3.0",
|
||||||
@@ -82,6 +84,8 @@
|
|||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"p-limit": "^3.1.0",
|
"p-limit": "^3.1.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"source-map-explorer": "^2.5.2"
|
"source-map-explorer": "^2.5.2",
|
||||||
|
"supertest": "^7.1.0",
|
||||||
|
"vitest": "^3.0.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,14 @@ exports.createShop = async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await client.request(
|
const result = await client.request(
|
||||||
`mutation INSERT_BODYSHOPS($bs: bodyshops_insert_input!){
|
`mutation INSERT_BODYSHOPS($bs: bodyshops_insert_input!) {
|
||||||
insert_bodyshops_one(object:$bs){
|
insert_bodyshops_one(object: $bs) {
|
||||||
id
|
id
|
||||||
|
vendors {
|
||||||
}
|
id
|
||||||
}`,
|
}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
{
|
{
|
||||||
bs: {
|
bs: {
|
||||||
...bodyshop,
|
...bodyshop,
|
||||||
@@ -54,12 +56,39 @@ exports.createShop = async (req, res) => {
|
|||||||
{ countertype: "ihbnum", count: 1 },
|
{ countertype: "ihbnum", count: 1 },
|
||||||
{ countertype: "paymentnum", count: 1 }
|
{ countertype: "paymentnum", count: 1 }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
vendors: {
|
||||||
|
data: [{ name: "In-House" }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
res.json(result);
|
const bodyshopId = result.insert_bodyshops_one.id;
|
||||||
|
const vendorId = result.insert_bodyshops_one.vendors[0].id;
|
||||||
|
|
||||||
|
if (!bodyshopId || !vendorId) {
|
||||||
|
throw new Error("Failed to create bodyshop or vendor");
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateBodyshop = await client.request(
|
||||||
|
`mutation UPDATE_BODYSHOP($id: uuid!, $inhousevendorid: uuid!) {
|
||||||
|
update_bodyshops_by_pk(pk_columns: { id: $id }, _set: { inhousevendorid: $inhousevendorid }) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
id: bodyshopId,
|
||||||
|
inhousevendorid: vendorId
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.status(200).json(updateBodyshop);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.log("admin-create-shop-error", "error", req.user.email, null, {
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
request: req.body,
|
||||||
|
ioadmin: true
|
||||||
|
});
|
||||||
res.status(500).json(error);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2768,6 +2768,9 @@ exports.GET_BODYSHOP_BY_ID = `
|
|||||||
id
|
id
|
||||||
md_order_statuses
|
md_order_statuses
|
||||||
shopname
|
shopname
|
||||||
|
imexshopid
|
||||||
|
intellipay_config
|
||||||
|
state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const getShopCredentials = async (bodyshop) => {
|
|||||||
const decodeComment = (comment) => {
|
const decodeComment = (comment) => {
|
||||||
try {
|
try {
|
||||||
return comment ? JSON.parse(Buffer.from(comment, "base64").toString()) : null;
|
return comment ? JSON.parse(Buffer.from(comment, "base64").toString()) : null;
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null; // Handle malformed base64 string gracefully
|
return null; // Handle malformed base64 string gracefully
|
||||||
}
|
}
|
||||||
@@ -361,24 +362,18 @@ exports.checkfee = async (req, res) => {
|
|||||||
exports.postback = async (req, res) => {
|
exports.postback = async (req, res) => {
|
||||||
const { body: values } = req;
|
const { body: values } = req;
|
||||||
const decodedComment = decodeComment(values?.comment);
|
const decodedComment = decodeComment(values?.comment);
|
||||||
|
|
||||||
const logResponseMeta = {
|
const logResponseMeta = {
|
||||||
bodyshop: {
|
|
||||||
id: req.body?.bodyshop?.id,
|
|
||||||
imexshopid: req.body?.bodyshop?.imexshopid,
|
|
||||||
name: req.body?.bodyshop?.shopname,
|
|
||||||
state: req.body?.bodyshop?.state
|
|
||||||
},
|
|
||||||
iprequest: values,
|
iprequest: values,
|
||||||
decodedComment
|
decodedComment
|
||||||
};
|
};
|
||||||
const ipMapping = req.body?.bodyshop?.intellipay_config?.payment_map;
|
|
||||||
|
|
||||||
logger.log("intellipay-postback-received", "DEBUG", req.user?.email, null, logResponseMeta);
|
logger.log("intellipay-postback-received", "DEBUG", "api", null, logResponseMeta);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ((!values.invoice || values.invoice === "") && !decodedComment) {
|
if ((!values.invoice || values.invoice === "") && !decodedComment) {
|
||||||
//invoice is specified through the pay link. Comment by IO.
|
//invoice is specified through the pay link. Comment by IO.
|
||||||
logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, {
|
logger.log("intellipay-postback-ignored", "DEBUG", "api", null, {
|
||||||
message: "No invoice or comment provided",
|
message: "No invoice or comment provided",
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
@@ -391,7 +386,7 @@ exports.postback = async (req, res) => {
|
|||||||
//This has been triggered by IO and may have multiple jobs.
|
//This has been triggered by IO and may have multiple jobs.
|
||||||
const parsedComment = decodedComment;
|
const parsedComment = decodedComment;
|
||||||
|
|
||||||
logger.log("intellipay-postback-parsed-comment", "DEBUG", req.user?.email, null, {
|
logger.log("intellipay-postback-parsed-comment", "DEBUG", "api", null, {
|
||||||
parsedComment,
|
parsedComment,
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
@@ -405,8 +400,12 @@ exports.postback = async (req, res) => {
|
|||||||
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
|
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
|
||||||
ids: partialPayments.map((p) => p.jobid)
|
ids: partialPayments.map((p) => p.jobid)
|
||||||
});
|
});
|
||||||
|
const bodyshop = await gqlClient.request(queries.GET_BODYSHOP_BY_ID, {
|
||||||
|
id: jobs.jobs[0].shopid
|
||||||
|
});
|
||||||
|
const ipMapping = bodyshop.bodyshops_by_pk.intellipay_config?.payment_map;
|
||||||
|
|
||||||
logger.log("intellipay-postback-jobs-fetched", "DEBUG", req.user?.email, null, {
|
logger.log("intellipay-postback-jobs-fetched", "DEBUG", "api", null, {
|
||||||
jobs,
|
jobs,
|
||||||
parsedComment,
|
parsedComment,
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
@@ -424,7 +423,7 @@ exports.postback = async (req, res) => {
|
|||||||
payment_responses: {
|
payment_responses: {
|
||||||
data: {
|
data: {
|
||||||
amount: values.total,
|
amount: values.total,
|
||||||
bodyshopid: jobs.jobs[0].shopid,
|
bodyshopid: bodyshop.bodyshops_by_pk.id,
|
||||||
jobid: p.jobid,
|
jobid: p.jobid,
|
||||||
declinereason: "Approved",
|
declinereason: "Approved",
|
||||||
ext_paymentid: values.paymentid,
|
ext_paymentid: values.paymentid,
|
||||||
@@ -435,7 +434,7 @@ exports.postback = async (req, res) => {
|
|||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log("intellipay-postback-payment-success", "DEBUG", req.user?.email, null, {
|
logger.log("intellipay-postback-payment-success", "DEBUG", "api", null, {
|
||||||
paymentResult,
|
paymentResult,
|
||||||
jobs,
|
jobs,
|
||||||
parsedComment,
|
parsedComment,
|
||||||
@@ -458,7 +457,7 @@ exports.postback = async (req, res) => {
|
|||||||
.join("<br/>")
|
.join("<br/>")
|
||||||
})
|
})
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
logger.log("intellipay-postback-email-error", "ERROR", req.user?.email, null, {
|
logger.log("intellipay-postback-email-error", "ERROR", "api", null, {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
jobs,
|
jobs,
|
||||||
paymentResult,
|
paymentResult,
|
||||||
@@ -472,8 +471,14 @@ exports.postback = async (req, res) => {
|
|||||||
id: values.invoice
|
id: values.invoice
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log("intellipay-postback-invoice-job-fetched", "DEBUG", req.user?.email, null, {
|
const bodyshop = await gqlClient.request(queries.GET_BODYSHOP_BY_ID, {
|
||||||
|
id: job.jobs_by_pk.shopid
|
||||||
|
});
|
||||||
|
const ipMapping = bodyshop.bodyshops_by_pk.intellipay_config?.payment_map;
|
||||||
|
|
||||||
|
logger.log("intellipay-postback-invoice-job-fetched", "DEBUG", "api", null, {
|
||||||
job,
|
job,
|
||||||
|
bodyshop,
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -488,7 +493,7 @@ exports.postback = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log("intellipay-postback-invoice-payment-success", "DEBUG", req.user?.email, null, {
|
logger.log("intellipay-postback-invoice-payment-success", "DEBUG", "api", null, {
|
||||||
paymentResult,
|
paymentResult,
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
@@ -496,7 +501,7 @@ exports.postback = async (req, res) => {
|
|||||||
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
|
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
|
||||||
paymentResponse: {
|
paymentResponse: {
|
||||||
amount: values.total,
|
amount: values.total,
|
||||||
bodyshopid: job.jobs_by_pk.shopid,
|
bodyshopid: bodyshop.bodyshops_by_pk.id,
|
||||||
paymentid: paymentResult.id,
|
paymentid: paymentResult.id,
|
||||||
jobid: values.invoice,
|
jobid: values.invoice,
|
||||||
declinereason: "Approved",
|
declinereason: "Approved",
|
||||||
@@ -506,14 +511,14 @@ exports.postback = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log("intellipay-postback-invoice-response-success", "DEBUG", req.user?.email, null, {
|
logger.log("intellipay-postback-invoice-response-success", "DEBUG", "api", null, {
|
||||||
responseResults,
|
responseResults,
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
|
logger.log("intellipay-postback-error", "ERROR", "api", null, {
|
||||||
message: error?.message,
|
message: error?.message,
|
||||||
...logResponseMeta
|
...logResponseMeta
|
||||||
});
|
});
|
||||||
|
|||||||
14
server/tests/api.test.js
Normal file
14
server/tests/api.test.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import request from "supertest";
|
||||||
|
import express from "express";
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.get("/api/health", (req, res) => res.json({ status: "ok" }));
|
||||||
|
|
||||||
|
describe("API", () => {
|
||||||
|
it("returns health status", async () => {
|
||||||
|
const response = await request(app).get("/api/health");
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body).toEqual({ status: "ok" });
|
||||||
|
});
|
||||||
|
});
|
||||||
11
server/tests/math.test.js
Normal file
11
server/tests/math.test.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
|
||||||
|
function add(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Math", () => {
|
||||||
|
it("adds two numbers correctly", () => {
|
||||||
|
expect(add(2, 3)).toBe(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
10
vitest.config.js
Normal file
10
vitest.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: "node",
|
||||||
|
globals: true,
|
||||||
|
include: ["./server/tests/**/*.{test,spec}.[jt]s"], // Only search /tests in root
|
||||||
|
exclude: ["**/client/**", "**/node_modules/**", "**/dist/**"] // Explicitly exclude /client
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user