const { test } = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const vm = require("node:vm"); const factory = new vm.Script(fs.readFileSync("src/services/fusion/account-contact.js", "utf8") + "\ncreateFusionAccountContact").runInNewContext(); function setup(response) { const calls=[], logs=[], failures=[], window={}; let tokens=0; const service=factory({ environment:{window, async fetch(url,options) { assert.equal(this,window,"Native fetch must use Window as its receiver"); calls.push({url,options}); return response instanceof Response ? response : Response.json(response); }}, getToken:async()=>{tokens++;return "test-relay-token";},logRequestStart:entry=>{logs.push(entry);return entry;}, logRequestSuccess:()=>{}, logRequestFailure:(_,error)=>failures.push(error),readResponseBodyForDebug:response=>response.clone().text() }); return {service,calls,logs,failures,tokens:()=>tokens}; } test("contact opportunities uses the person's PartyNumber, paging and contact-query headers",async()=>{ const h=setup({items:[{OptyNumber:"A56DLD"}],hasMore:true,totalResults:12}); const data=await h.service.requestOpportunitiesByContact("18725565",5); assert.equal(data.items[0].OptyNumber,"A56DLD"); const {url,options}=h.calls[0], parsed=new URL(url); assert.equal(parsed.searchParams.get("q"),"(OpportunityContact.ContactPartyNumber='18725565')"); assert.equal(parsed.searchParams.get("offset"),"5");assert.equal(parsed.searchParams.get("limit"),"5"); assert.equal(options.method,"GET");assert.equal(options.headers.Authorization,"Bearer test-relay-token"); assert.equal(options.headers.Preference,"transient");assert.equal(options.headers.Origin,"https://eeho.fa.us2.oraclecloud.com"); assert.equal(h.logs[0].name,"requestOpportunitiesByContact"); assert.equal(h.logs[0].url,url); assert.ok(url.includes("q=%28OpportunityContact.ContactPartyNumber%3D%2718725565%27%29")); await assert.rejects(h.service.requestOpportunitiesByContact(undefined),/PartyNumber/); assert.equal(h.calls.length,1); }); test("contact identity lookup uses exact PartyId without name keyword ranking",async()=>{ const h=setup({items:[{PartyId:"123",PartyName:"New Name",PartyNumber:"789"}]}); const contacts=await h.service.requestQueryAccountContact(456,"Old Name",()=>true,"123"); const body=JSON.parse(h.calls[0].options.body); assert.equal(body.keywords,"");assert.deepEqual(body.q.criteria.at(-1),{attribute:"PartyId",op:"$eq",value:"123"}); assert.equal(contacts[0].PartyNumber,"789"); }); test("contact opportunity query preserves an alphanumeric PartyNumber",async()=>{ const h=setup({items:[],hasMore:false}); await h.service.requestOpportunitiesByContact("A4KCS2P"); assert.equal(new URL(h.calls[0].url).searchParams.get("q"),"(OpportunityContact.ContactPartyNumber='A4KCS2P')"); assert.equal(h.logs[0].metadata.partyNumber,"A4KCS2P"); }); test("opportunities resolution failures are logged before any HTTP request",async()=>{ const h=setup({items:[]}); await assert.rejects(h.service.requestOpportunitiesByContact(null,0,{contactPartyId:"123",contactName:"Alex",resolvePartyNumber:async()=>undefined}),/HTTP request was not sent/); assert.equal(h.calls.length,0);assert.equal(h.tokens(),0); assert.equal(h.logs[0].name,"resolveContactForOpportunities");assert.equal(h.logs[0].method,"LOCAL");assert.equal(h.logs[0].metadata.httpRequestSent,false); assert.equal(h.logs[0].metadata.contactPartyId,"123");assert.equal(h.failures.length,1); await assert.rejects(h.service.requestOpportunitiesByContact(null,0,{resolvePartyNumber:async()=>{throw new Error("Contact query failed");}}),/Contact query failed/); assert.equal(h.failures.length,2); }); test("contact search posts the specified payload with dynamic PartyId, keywords and Token Relay bearer token",async()=>{ const h=setup({items:[{PartyId:123,PartyName:"Same Name",JobTitle:"Architect"},{PartyId:456,PartyName:"Same Name",JobTitle:null}]}); const result=await h.service.requestQueryAccountContact("100000810210835",'Alex "Cloud"'); assert.equal(result.length,2); assert.equal(result[0].PartyId,"123"); const {url,options}=h.calls[0]; assert.ok(url.endsWith("/crmRestApi/searchResources/latest/custom-actions/queries")); assert.equal(options.method,"POST");assert.equal(options.headers.Authorization,"Bearer test-relay-token"); assert.equal(options.headers["Content-Type"],"application/json");assert.equal(options.credentials,"include"); assert.equal(options.headers.Preference,"transient"); assert.equal(options.headers.Origin,"https://eeho.fa.us2.oraclecloud.com"); const fields=["PartyName","PrimaryEmail.EmailAddress","JobTitle","PrimaryAccount.PartyName","PartyId","PartyNumber"]; assert.deepEqual(JSON.parse(options.body),{aggregationResults:false,skipInValidFields:true,skipHiddenFromUIFields:true,language:"en",onlyData:true, keywords:'Alex "Cloud"',entity:"Contact",fields,keywordsFields:fields, q:{op:"$and",criteria:[{op:"$or",innerQuery:true,criteria:[{attribute:"PrimaryAccount.PartyId",op:"$eq",value:100000810210835}]}],innerQuery:true}, sort:[{attribute:"_score",direction:"descending"}],limit:15,offset:0}); assert.equal(h.logs[0].name,"requestQueryAccountContact");assert.equal(h.calls.length,1); }); test("contact search skips blank or obsolete queries and never sends a query without an account",async()=>{ const h=setup({items:[]}); await h.service.requestQueryAccountContact(123," "); await h.service.requestQueryAccountContact(123,"Alex",()=>false); await assert.rejects(h.service.requestQueryAccountContact(undefined,"Alex"),/PartyId/); assert.equal(h.calls.length,0);assert.equal(h.tokens(),0); }); test("contact search reports HTTP and malformed-response errors",async()=>{ for(const response of [new Response("Denied",{status:401}),{items:null}]){ const h=setup(response);await assert.rejects(h.service.requestQueryAccountContact(123,"Alex")); assert.equal(h.calls.length,1);assert.equal(h.failures.length,1); } }); test("contact creation maps account, phone and mutually exclusive contact preferences", async()=>{ for (const preference of ["none", "deny", "allow"]) { const h=setup({PartyId:321}); await h.service.requestPersistContact({PartyId:123,PartyNumber:"456"},{firstName:" Alex ",lastName:" Test ",jobTitle:"Engineer",email:"alex@example.test", emailConsent:preference,workConsent:preference,mobileConsent:preference,workPhone:"3131313131",workCountry:"55",workExtension:"3131",mobilePhone:"31999999999",mobileCountry:"55"}); const {url,options}=h.calls[0], payload=JSON.parse(options.body); assert.ok(url.endsWith("/contacts?onlyData=true")); assert.equal(options.method,"POST"); assert.equal(options.headers.Authorization,"Bearer test-relay-token");assert.equal(options.headers.Origin,"https://eeho.fa.us2.oraclecloud.com"); assert.equal(payload.AccountPartyId,"123");assert.equal(payload.AccountPartyNumber,"456");assert.equal(payload.FirstName,"Alex"); assert.equal(payload.RawWorkPhoneNumber,"55-3131313131");assert.equal(payload.WorkPhoneExtension,"3131");assert.equal(payload.RawMobileNumber,"55-31999999999"); for(const key of ["DoCallWorkPhoneFlag","DoCallMobilePhoneFlag","DoContactEmailFlag"]) assert.equal(payload[key],preference==="none"?undefined:preference==="allow"); for(const key of ["DoNotCallWorkPhoneFlag","DoNotCallMobileFlag","DoNotContactEmailFlag"]) assert.equal(payload[key],preference==="none"?undefined:preference==="deny"); assert.equal(h.logs[0].name,"requestPersistContact"); } }); test("contact creation reports failed POST without automatic retries",async()=>{ const h=setup(new Response("Denied",{status:403})); await assert.rejects(h.service.requestPersistContact({PartyId:123,PartyNumber:"456"},{firstName:"Alex",lastName:"Test",jobTitle:"",email:"alex@example.test",workPhone:"",workCountry:"",workExtension:"",mobilePhone:"",mobileCountry:""}),/403/); assert.equal(h.calls.length,1);assert.equal(h.failures.length,1); }); test("contact creation sends directly and omits empty optional fields", async()=>{ const values={firstName:"Alex",lastName:"Test",jobTitle:"",email:"alex@example.test",workPhone:"",workCountry:"",workExtension:"",mobilePhone:"",mobileCountry:""}; const h=setup({PartyId:321}); await h.service.requestPersistContact({PartyId:123,PartyNumber:"456"},values); assert.equal(h.calls.length,1);assert.equal(h.tokens(),1); assert.deepEqual(JSON.parse(h.calls[0].options.body),{FirstName:"Alex",LastName:"Test",AccountPartyId:"123",AccountPartyNumber:"456",Type:"ZCA_CONTACT",EmailAddress:"alex@example.test"}); });