Files
bodyshop/server/rr/rr-test.js

266 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* RR smoke test / CLI (STAR-only)
*/
const path = require("path");
const fs = require("fs");
const dotenv = require("dotenv");
const { GraphQLClient, gql } = require("graphql-request");
const { MakeRRCall, renderXmlTemplate, buildStarEnvelope } = require("./rr-helpers");
const { getBaseRRConfig } = require("./rr-constants");
// Load env file for local runs
const defaultEnvPath = path.resolve(__dirname, "../../.env.development");
if (fs.existsSync(defaultEnvPath)) {
const result = dotenv.config({ path: defaultEnvPath });
if (result?.parsed) {
console.log(
`${defaultEnvPath}\n[dotenv@${require("dotenv/package.json").version}] injecting env (${Object.keys(result.parsed).length}) from ../../.env.development`
);
}
}
// ---- CLI args parsing ----
const argv = process.argv.slice(2);
const args = { _: [] };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith("--")) {
const eq = a.indexOf("=");
if (eq > -1) {
const k = a.slice(2, eq);
const v = a.slice(eq + 1);
args[k] = v;
} else {
const k = a.slice(2);
const next = argv[i + 1];
if (next && !next.startsWith("-")) {
args[k] = next;
i++;
} else {
args[k] = true;
}
}
} else if (a.startsWith("-") && a.length > 1) {
const k = a.slice(1);
const next = argv[i + 1];
if (next && !next.startsWith("-")) {
args[k] = next;
i++;
} else {
args[k] = true;
}
} else {
args._.push(a);
}
}
function toIntOr(defaultVal, maybe) {
const n = parseInt(maybe, 10);
return Number.isFinite(n) ? n : defaultVal;
}
// ---------------- GraphQL helpers ----------------
function buildGqlClient() {
const endpoint = process.env.GRAPHQL_ENDPOINT;
if (!endpoint) throw new Error("GRAPHQL_ENDPOINT env var is required when using --bodyshopId.");
const headers = {};
if (process.env.HASURA_ADMIN_SECRET) {
headers["x-hasura-admin-secret"] = process.env.HASURA_ADMIN_SECRET;
} else if (process.env.GRAPHQL_BEARER) {
headers["authorization"] = `Bearer ${process.env.GRAPHQL_BEARER}`;
}
return new GraphQLClient(endpoint, { headers });
}
const Q_BODYSHOPS_BY_PK = gql`
query BodyshopRR($id: uuid!) {
bodyshops_by_pk(id: $id) {
rr_dealerid
rr_configuration
}
}
`;
function normalizeConfigRecord(rec) {
if (!rec) return null;
let cfg = rec.rr_configuration || {};
if (typeof cfg === "string") {
try {
cfg = JSON.parse(cfg);
} catch {
cfg = {};
}
}
return {
dealerNumber: rec.rr_dealerid || undefined,
storeNumber: cfg.storeNumber || undefined,
branchNumber: cfg.branchNumber || undefined
};
}
/**
* Load RR config overrides from DB (bodyshops_by_pk only).
*/
async function loadBodyshopRRConfig(bodyshopId) {
if (!bodyshopId) return null;
const client = buildGqlClient();
const { bodyshops_by_pk: bs } = await client.request(Q_BODYSHOPS_BY_PK, { id: bodyshopId });
if (!bs) throw new Error("Bodyshop not found.");
if (!bs.rr_dealerid) throw new Error("Bodyshop is not configured for RR (missing rr_dealerid).");
return normalizeConfigRecord(bs);
}
// ---------------- rr-test logic ----------------
function pickActionName(raw) {
if (!raw || typeof raw !== "string") return "ping";
const x = raw.toLowerCase();
if (x === "combined" || x === "combinedsearch" || x === "comb") return "combined";
if (x === "advisors" || x === "advisor" || x === "getadvisors") return "advisors";
if (x === "parts" || x === "getparts" || x === "part") return "parts";
if (x === "ping") return "ping";
return x;
}
function buildBodyForAction(action, args, cfg) {
switch (action) {
case "ping":
case "advisors": {
const max = toIntOr(1, args.max);
const data = {
DealerCode: cfg.dealerNumber,
DealerNumber: cfg.dealerNumber,
StoreNumber: cfg.storeNumber,
BranchNumber: cfg.branchNumber,
SearchCriteria: {
AdvisorId: args.advisorId,
FirstName: args.first || args.firstname,
LastName: args.last || args.lastname,
Department: args.department,
Status: args.status || "ACTIVE",
IncludeInactive: args.includeInactive ? "true" : undefined,
MaxResults: max
}
};
return { template: "GetAdvisors", data, appArea: {} };
}
case "combined": {
const max = toIntOr(10, args.max);
const data = {
DealerNumber: cfg.dealerNumber,
StoreNumber: cfg.storeNumber,
BranchNumber: cfg.branchNumber,
Customer: {
FirstName: args.first,
LastName: args.last,
PhoneNumber: args.phone,
EmailAddress: args.email
},
Vehicle: {
VIN: args.vin,
LicensePlate: args.plate
},
MaxResults: max
};
return { template: "CombinedSearch", data, appArea: {} };
}
case "parts": {
const max = toIntOr(5, args.max);
const data = {
DealerNumber: cfg.dealerNumber,
StoreNumber: cfg.storeNumber,
BranchNumber: cfg.branchNumber,
SearchCriteria: {
PartNumber: args.part,
Description: args.desc,
Make: args.make,
Model: args.model,
Year: args.year,
MaxResults: max
}
};
return { template: "GetParts", data, appArea: {} };
}
default:
throw new Error(`Unsupported action: ${action}`);
}
}
async function main() {
const action = pickActionName(args.action || args.a || args._[0]);
const bodyshopId = args.bodyshopId || args.bodyshop || args.b;
const rrAction =
action === "ping"
? "GetAdvisors"
: action === "advisors"
? "GetAdvisors"
: action === "combined"
? "CombinedSearch"
: action === "parts"
? "GetParts"
: action;
// Start with env-based defaults…
const cfg = getBaseRRConfig();
// …then override with per-bodyshop values if provided.
if (bodyshopId) {
try {
const overrides = await loadBodyshopRRConfig(bodyshopId);
if (overrides?.dealerNumber) cfg.dealerNumber = overrides.dealerNumber;
if (overrides?.storeNumber) cfg.storeNumber = overrides.storeNumber;
if (overrides?.branchNumber) cfg.branchNumber = overrides.branchNumber;
console.log(" RR config loaded from DB via: bodyshops_by_pk");
} catch (e) {
console.error("❌ Failed to load per-bodyshop RR config:", e.message);
process.exit(1);
}
}
const body = buildBodyForAction(action, args, cfg);
const templateName = body.template || rrAction;
try {
await renderXmlTemplate(templateName, body.data);
console.log("✅ Templates verified.");
} catch (e) {
console.error("❌ Template verification failed:", e.message);
process.exit(1);
}
if (args.dry) {
const business = await renderXmlTemplate(templateName, body.data);
const envelope = await buildStarEnvelope(business, cfg, body.appArea);
console.log("\n--- FULL SOAP ENVELOPE ---\n");
console.log(envelope);
console.log("\n(dry run) 🚫 Skipping network call.");
return;
}
try {
console.log(`\n▶ Calling Rome action: ${rrAction}`);
const xml = await MakeRRCall({ action: rrAction, body, dealerConfig: cfg });
console.log("\n✅ RR call succeeded.\n");
console.log(xml);
} catch (err) {
console.dir(err);
console.error("[RR] rr-test failed", { message: err.message, stack: err.stack });
process.exit(1);
}
}
main().catch(() => process.exit(1));