feature/IO-3357-Reynolds-and-Reynolds-DMS-API-Integration - Checkpoint
This commit is contained in:
@@ -1,127 +1,191 @@
|
||||
// node server/rr/rr-test.js
|
||||
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @file rr-test.js
|
||||
* @description Diagnostic test script for Reynolds & Reynolds (R&R) integration.
|
||||
* Run with: NODE_ENV=development node server/rr/rr-test.js
|
||||
* RR smoke test / CLI (STAR-only)
|
||||
*/
|
||||
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(__dirname, "../../", `.env.${process.env.NODE_ENV || "development"}`)
|
||||
});
|
||||
|
||||
const fs = require("fs/promises");
|
||||
const mustache = require("mustache");
|
||||
const fs = require("fs");
|
||||
const dotenv = require("dotenv");
|
||||
const { MakeRRCall, renderXmlTemplate, buildStarEnvelope } = require("./rr-helpers");
|
||||
const { getBaseRRConfig } = require("./rr-constants");
|
||||
const { RRActions, MakeRRCall } = require("./rr-helpers");
|
||||
const RRLogger = require("./rr-logger");
|
||||
|
||||
// --- Mock socket + redis helpers for standalone test
|
||||
const socket = {
|
||||
bodyshopId: process.env.TEST_BODYSHOP_ID || null,
|
||||
user: { email: "test@romeonline.io" },
|
||||
emit: (event, data) => console.log(`[SOCKET EVENT] ${event}`, data),
|
||||
logger: console
|
||||
};
|
||||
// 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`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const redisHelpers = {
|
||||
setSessionData: async () => {},
|
||||
getSessionData: async () => {},
|
||||
setSessionTransactionData: async () => {},
|
||||
getSessionTransactionData: async () => {},
|
||||
clearSessionTransactionData: async () => {}
|
||||
};
|
||||
// Parse CLI args
|
||||
const argv = process.argv.slice(2);
|
||||
const args = { _: [] };
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
console.log("=== Reynolds & Reynolds Integration Test ===");
|
||||
console.log("NODE_ENV:", process.env.NODE_ENV);
|
||||
|
||||
const baseCfg = getBaseRRConfig();
|
||||
console.log("Base R&R Config (from env):", {
|
||||
baseUrl: baseCfg.baseUrl,
|
||||
hasUser: !!baseCfg.username || !!process.env.RR_API_USER || !!process.env.RR_USERNAME,
|
||||
hasPass: !!baseCfg.password || !!process.env.RR_API_PASS || !!process.env.RR_PASSWORD,
|
||||
timeout: baseCfg.timeout
|
||||
});
|
||||
|
||||
// ---- test variables for GetAdvisors
|
||||
const templateVars = {
|
||||
DealerCode: process.env.RR_DEALER_NAME || "ROME",
|
||||
DealerName: "Rome Collision Test",
|
||||
SearchCriteria: {
|
||||
Department: "Body Shop",
|
||||
Status: "ACTIVE"
|
||||
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++; // consume value
|
||||
} else {
|
||||
args[k] = true; // boolean flag
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (a.startsWith("-") && a.length > 1) {
|
||||
// simple short flag handling: -a value
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Dealer/Store/Branch/PPSysId can come from rr_configuration or env; for test we override:
|
||||
const dealerConfigOverride = {
|
||||
// baseUrl can also be overridden here if you want
|
||||
ppsysid: process.env.RR_PPSYSID || process.env.RR_PP_SYS_ID || process.env.RR_PP_SYSID || "TEST-PPSYSID",
|
||||
dealer_number: process.env.RR_DEALER_NUMBER || "12345",
|
||||
store_number: process.env.RR_STORE_NUMBER || "01",
|
||||
branch_number: process.env.RR_BRANCH_NUMBER || "001",
|
||||
// creds (optional here; MakeRRCall will fallback to env if omitted)
|
||||
username: process.env.RR_API_USER || process.env.RR_USERNAME || "Rome",
|
||||
password: process.env.RR_API_PASS || process.env.RR_PASSWORD || "secret"
|
||||
};
|
||||
function toIntOr(defaultVal, maybe) {
|
||||
const n = parseInt(maybe, 10);
|
||||
return Number.isFinite(n) ? n : defaultVal;
|
||||
}
|
||||
|
||||
// Show the first ~600 chars of the envelope we will send (by rendering the template + header)
|
||||
// NOTE: This is just for printing; MakeRRCall will rebuild with proper header internally.
|
||||
const templatePath = path.join(__dirname, "xml-templates", "GetAdvisors.xml");
|
||||
const tpl = await fs.readFile(templatePath, "utf8");
|
||||
const renderedBody = mustache.render(tpl, templateVars);
|
||||
// ✅ fixed guard clause
|
||||
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;
|
||||
}
|
||||
|
||||
// Build a preview envelope using the same helper used by MakeRRCall
|
||||
const { renderXmlTemplate } = require("./rr-helpers");
|
||||
const headerPreview = await renderXmlTemplate("_EnvelopeHeader", {
|
||||
PPSysId: dealerConfigOverride.ppsysid,
|
||||
DealerNumber: dealerConfigOverride.dealer_number,
|
||||
StoreNumber: dealerConfigOverride.store_number,
|
||||
BranchNumber: dealerConfigOverride.branch_number,
|
||||
Username: dealerConfigOverride.username,
|
||||
Password: dealerConfigOverride.password,
|
||||
CorrelationId: "preview-correlation"
|
||||
});
|
||||
const previewEnvelope = `
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:rr="http://reynoldsandrey.com/">
|
||||
<soapenv:Header>
|
||||
${headerPreview}
|
||||
</soapenv:Header>
|
||||
<soapenv:Body>
|
||||
${renderedBody}
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>`.trim();
|
||||
|
||||
console.log("\n--- Rendered SOAP Envelope (first 600 chars) ---\n");
|
||||
console.log(previewEnvelope.slice(0, 600));
|
||||
console.log("... [truncated]\n");
|
||||
|
||||
// If we don't have a base URL, skip the live call
|
||||
if (!baseCfg.baseUrl) {
|
||||
console.warn("\n⚠️ No RR baseUrl defined. Skipping live call.\n");
|
||||
return;
|
||||
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: {} };
|
||||
}
|
||||
|
||||
console.log(`--- Sending SOAP Request: ${RRActions.GetAdvisors.action} ---\n`);
|
||||
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: {} };
|
||||
}
|
||||
|
||||
const responseXml = await MakeRRCall({
|
||||
action: "GetAdvisors",
|
||||
baseUrl: process.env.RR_API_BASE_URL,
|
||||
body: { template: "GetAdvisors", data: templateVars },
|
||||
dealerConfig: dealerConfigOverride,
|
||||
redisHelpers,
|
||||
socket,
|
||||
jobid: "test-job",
|
||||
retries: 1
|
||||
});
|
||||
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: {} };
|
||||
}
|
||||
|
||||
RRLogger(socket, "info", "RR test successful", { bytes: Buffer.byteLength(responseXml, "utf8") });
|
||||
console.log("\n✅ Test completed successfully.\n");
|
||||
} catch (error) {
|
||||
console.error("\n❌ Test failed:", error.message);
|
||||
console.error(error.stack);
|
||||
default:
|
||||
throw new Error(`Unsupported action: ${action}`);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const action = pickActionName(args.action || args.a || args._[0]);
|
||||
const rrAction =
|
||||
action === "ping"
|
||||
? "GetAdvisors"
|
||||
: action === "advisors"
|
||||
? "GetAdvisors"
|
||||
: action === "combined"
|
||||
? "CombinedSearch"
|
||||
: action === "parts"
|
||||
? "GetParts"
|
||||
: action;
|
||||
|
||||
const cfg = getBaseRRConfig();
|
||||
const body = buildBodyForAction(action, args, cfg);
|
||||
const templateName = body.template || rrAction;
|
||||
|
||||
try {
|
||||
const xml = 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((e) => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user