Primeiro Commit v0.beta1
This commit is contained in:
30
tests/README.md
Normal file
30
tests/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Knowledge One View CLI Tests
|
||||
|
||||
This folder keeps the skill tests intentionally small and executable.
|
||||
|
||||
The suite covers only two themes:
|
||||
|
||||
1. `functions.test.mjs` checks local functions and output contracts.
|
||||
2. `sales-prompts.test.mjs` checks whether common sales prompts map to quick, documented CLI commands for Oracle DV.
|
||||
|
||||
The tests read `SKILL.md` and `references/*.md` as the source of truth. They should not keep a parallel copy of long parameter lists, aliases, defaults, or week mappings.
|
||||
|
||||
Run offline tests:
|
||||
|
||||
```sh
|
||||
node tests/run.mjs
|
||||
```
|
||||
|
||||
Run live backend checks:
|
||||
|
||||
```sh
|
||||
KNOWLEDGE_ONE_VIEW_LIVE=1 \
|
||||
ORACLE_SI_RUNTIME_HOME=/path/to/workspace/knowledge-one-view-cli/runtime \
|
||||
node tests/run.mjs
|
||||
```
|
||||
|
||||
Offline mode does not call Oracle DV. Live mode starts with a no-cache session-context read, then runs prompt-sized backend checks. Prompts that should be org-wide commercial reads assert the primary source from `references/api-map.md`, reject fallback to `DV - SE Team`, and use larger live limits when row volume is part of the expected behavior.
|
||||
|
||||
Every test invocation of `fetch-sales-intelligence.mjs` is forced through `--no-cache`.
|
||||
|
||||
When `ORACLE_SI_RUNTIME_HOME` is set, the tests copy `auth-state.json` and `session-meta.json` into a temporary runtime first, so live checks do not rewrite the source runtime. Set `ORACLE_SI_TEST_RUNTIME_HOME` when you intentionally want to provide a writable test runtime.
|
||||
597
tests/functions.test.mjs
Normal file
597
tests/functions.test.mjs
Normal file
@@ -0,0 +1,597 @@
|
||||
import test, { after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
catalogSaysApiMetricsAreRawRows,
|
||||
catalogSaysCurrentWorkloadDefaults,
|
||||
catalogSaysPreserveMixedRows,
|
||||
catalogSaysUseRevenueLineCloseDate,
|
||||
parseDefaultWeekYear,
|
||||
parseCurrentWorkloadDefaults,
|
||||
parsePrimaryCommercialSubjectArea,
|
||||
parseSaAttachAliases,
|
||||
parseSaAttachDateWindowField,
|
||||
parseSaAttachDefaults,
|
||||
parseWeekRow,
|
||||
readSkillCatalogs,
|
||||
} from "./helpers/catalogs.mjs";
|
||||
import { skillHome } from "./helpers/cli.mjs";
|
||||
|
||||
const catalogs = await readSkillCatalogs();
|
||||
const fetchModule = await import(
|
||||
pathToFileURL(path.join(skillHome, "scripts", "fetch-sales-intelligence.mjs")).href
|
||||
);
|
||||
|
||||
const functionTestRows = [];
|
||||
|
||||
function functionTest(id, title, details, fn) {
|
||||
test(title, async () => {
|
||||
const row = {
|
||||
id,
|
||||
teste: title,
|
||||
entrada: details.entrada,
|
||||
saida: details.saida || "",
|
||||
avaliacao: "PASS",
|
||||
};
|
||||
try {
|
||||
const output = await fn();
|
||||
if (output) row.saida = output;
|
||||
} catch (error) {
|
||||
row.avaliacao = `FAIL; ${error.message}`;
|
||||
throw error;
|
||||
} finally {
|
||||
functionTestRows.push(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
after((t) => {
|
||||
t.diagnostic(formatFunctionResultsTable(functionTestRows));
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"auth-refresh-json-stdout",
|
||||
"automatic auth refresh notice is emitted on stderr",
|
||||
{
|
||||
entrada: "source scan for the automatic refresh notice in fetch-sales-intelligence.mjs",
|
||||
},
|
||||
async () => {
|
||||
const source = await fs.readFile(
|
||||
path.join(skillHome, "scripts", "fetch-sales-intelligence.mjs"),
|
||||
"utf8"
|
||||
);
|
||||
const notice = "Oracle DV session is missing or expired. Opening browser login to refresh auth...";
|
||||
assert.match(source, new RegExp(`console\\.error\\(${JSON.stringify(notice)}\\)`));
|
||||
assert.doesNotMatch(source, new RegExp(`console\\.log\\(${JSON.stringify(notice)}\\)`));
|
||||
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
const stdoutMessages = [];
|
||||
const stderrMessages = [];
|
||||
console.log = (...args) => stdoutMessages.push(args.join(" "));
|
||||
console.error = (...args) => stderrMessages.push(args.join(" "));
|
||||
try {
|
||||
await fetchModule.withConsoleLogRoutedToStderr(async () => {
|
||||
console.log("helper progress");
|
||||
});
|
||||
} finally {
|
||||
console.log = originalLog;
|
||||
console.error = originalError;
|
||||
}
|
||||
assert.deepEqual(stdoutMessages, []);
|
||||
assert.deepEqual(stderrMessages, ["helper progress"]);
|
||||
return "auto-refresh notice and imported helper progress use stderr, preserving JSON-only stdout";
|
||||
}
|
||||
);
|
||||
|
||||
functionTest(
|
||||
"date-window",
|
||||
"date-window functions follow documented shortcuts and week catalog",
|
||||
{
|
||||
entrada: "resolveDateWindow com --date, --month, --quarter, --week e combinação inválida",
|
||||
},
|
||||
() => {
|
||||
const exact = fetchModule.resolveDateWindow({ date: "2026-05-02" });
|
||||
assert.equal(exact.preset, "single-date");
|
||||
assert.equal(exact.fromDate, "2026-05-02");
|
||||
assert.equal(exact.toDate, "2026-05-02");
|
||||
|
||||
const leapDay = fetchModule.resolveDateWindow({ date: "2024-02-29" });
|
||||
assert.equal(leapDay.fromDate, "2024-02-29");
|
||||
assert.equal(leapDay.toDate, "2024-02-29");
|
||||
|
||||
assert.throws(
|
||||
() => fetchModule.resolveDateWindow({ date: "2026-99-99" }),
|
||||
/Invalid calendar date "2026-99-99"/
|
||||
);
|
||||
assert.throws(
|
||||
() => fetchModule.resolveDateWindow({ date: "2026-02-29" }),
|
||||
/Invalid calendar date "2026-02-29"/
|
||||
);
|
||||
|
||||
const apiaCheck = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
"--input-type=module",
|
||||
"-e",
|
||||
`
|
||||
const fetchModule = await import(${JSON.stringify(pathToFileURL(path.join(skillHome, "scripts", "fetch-sales-intelligence.mjs")).href)});
|
||||
const window = fetchModule.resolveDateWindow({ date: "2011-12-30" });
|
||||
console.log(window.fromDate);
|
||||
`,
|
||||
],
|
||||
{
|
||||
env: { ...process.env, TZ: "Pacific/Apia" },
|
||||
encoding: "utf8",
|
||||
}
|
||||
);
|
||||
assert.equal(apiaCheck.status, 0, apiaCheck.stderr);
|
||||
assert.equal(apiaCheck.stdout.trim(), "2011-12-30");
|
||||
|
||||
const month = fetchModule.resolveDateWindow({ month: "2026-04" });
|
||||
assert.equal(month.preset, "calendar-month");
|
||||
assert.equal(month.fromDate, "2026-04-01");
|
||||
assert.equal(month.toDate, "2026-04-30");
|
||||
|
||||
const quarter = fetchModule.resolveDateWindow({ quarter: "FY26-Q4" });
|
||||
assert.equal(quarter.preset, "fiscal-quarter");
|
||||
assert.equal(quarter.fiscalYear, "FY26");
|
||||
assert.equal(quarter.fiscalQuarter, "Q4");
|
||||
assert.equal(quarter.fromDate, "2026-03-01");
|
||||
assert.equal(quarter.toDate, "2026-05-31");
|
||||
|
||||
const weekYear = parseDefaultWeekYear(catalogs.scriptCatalog);
|
||||
const expectedWeek = parseWeekRow(catalogs.scriptCatalog, "W43");
|
||||
const week = fetchModule.resolveDateWindow({
|
||||
week: expectedWeek.weekLabel.toLowerCase(),
|
||||
"week-year": String(weekYear),
|
||||
});
|
||||
assert.equal(week.weekLabel, expectedWeek.weekLabel);
|
||||
assert.equal(week.fromDate, expectedWeek.fromDate);
|
||||
assert.equal(week.toDate, expectedWeek.toDate);
|
||||
|
||||
assert.throws(
|
||||
() => fetchModule.resolveDateWindow({ date: "2026-05-02", month: "2026-05" }),
|
||||
/Use either --date/
|
||||
);
|
||||
return `single=${exact.fromDate}; month=${month.fromDate}..${month.toDate}; quarter=${quarter.fromDate}..${quarter.toDate}; week=${week.fromDate}..${week.toDate}; invalid-combo rejected`;
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"sa-attach-defaults",
|
||||
"SA Attach defaults and aliases come from initial-context-catalog",
|
||||
{
|
||||
entrada: "parseSaAttachDefaults/parseSaAttachAliases + resolveSaAttachFilters",
|
||||
},
|
||||
() => {
|
||||
const defaults = parseSaAttachDefaults(catalogs.initialContext);
|
||||
const aliases = parseSaAttachAliases(catalogs.initialContext);
|
||||
|
||||
const filters = fetchModule.resolveSaAttachFilters({}, {
|
||||
defaultStatuses: defaults.revenueLineStatus,
|
||||
});
|
||||
assert.equal(filters.cluster, defaults.cluster[0]);
|
||||
assert.deepEqual(filters.solutions, defaults.solution);
|
||||
assert.deepEqual(filters.statuses, defaults.revenueLineStatus);
|
||||
assert.deepEqual(filters.productLobs, defaults.executiveProductLob);
|
||||
assert.deepEqual(filters.revenueTypeGroups, defaults.revenueTypeGroup);
|
||||
assert.equal(filters.salesCreditType, defaults.salesCreditType[0]);
|
||||
assert.equal(filters.minOpportunityProbability, Number(defaults.minOpportunityProbability[0]));
|
||||
|
||||
const lobAlias = Object.entries(aliases).find(([, values]) => values.length > 1);
|
||||
const targetRevenueType = defaults.revenueTypeGroup.at(-1);
|
||||
const revenueTypeAlias = Object.entries(aliases).find(([, values]) => values.includes(targetRevenueType));
|
||||
assert.ok(lobAlias, "Expected at least one multi-value LOB alias in the catalog.");
|
||||
assert.ok(revenueTypeAlias, "Expected a revenue-type alias for the catalog default workload group.");
|
||||
|
||||
const aliasFilters = fetchModule.resolveSaAttachFilters(
|
||||
{
|
||||
"executive-product-lob": lobAlias[0],
|
||||
"revenue-type-group": revenueTypeAlias[0].split("|")[0],
|
||||
},
|
||||
{ defaultStatuses: defaults.revenueLineStatus }
|
||||
);
|
||||
assert.deepEqual(aliasFilters.productLobs, lobAlias[1]);
|
||||
assert.deepEqual(aliasFilters.revenueTypeGroups, revenueTypeAlias[1]);
|
||||
return `cluster=${filters.cluster}; solutions=${filters.solutions.length}; statuses=${filters.statuses.length}; aliases validated`;
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"close-window-explicit-cluster",
|
||||
"close-window commercial SQL honors explicit cluster without SA Attach defaults",
|
||||
{
|
||||
entrada: "buildPrimaryOpportunityWhereClauses com cluster Mexico",
|
||||
},
|
||||
() => {
|
||||
const clauses = fetchModule.buildPrimaryOpportunityWhereClauses({
|
||||
fromDateSql: "DATE '2026-05-01'",
|
||||
toDateSql: "DATE '2026-05-31'",
|
||||
statuses: ["Open"],
|
||||
cluster: "Mexico",
|
||||
});
|
||||
|
||||
const sql = clauses.join("\n");
|
||||
assert.match(sql, /"Revenue Line Close Date" >= DATE '2026-05-01'/);
|
||||
assert.match(sql, /"Revenue Line Close Date" <= DATE '2026-05-31'/);
|
||||
assert.match(sql, /"Opportunity Status" IN \('Open'\)/);
|
||||
assert.match(sql, /"Cluster" = 'Mexico'/);
|
||||
assert.doesNotMatch(sql, /"Solution" IN/);
|
||||
assert.doesNotMatch(sql, /"Revenue Type Group" IN/);
|
||||
assert.doesNotMatch(sql, /"Sales Credit Type" =/);
|
||||
|
||||
const withoutCluster = fetchModule.buildPrimaryOpportunityWhereClauses({
|
||||
fromDateSql: "DATE '2026-05-01'",
|
||||
toDateSql: "DATE '2026-05-31'",
|
||||
statuses: ["Open"],
|
||||
}).join("\n");
|
||||
assert.doesNotMatch(withoutCluster, /"Cluster" =/);
|
||||
|
||||
return "explicit cluster is applied; absent cluster preserves existing unfiltered geography behavior";
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"manager-resource-workload-explicit-cluster",
|
||||
"manager/resource workload SQL honors explicit cluster and defaults to Brazil",
|
||||
{
|
||||
entrada: "buildManagerResourceWorkloadSql com cluster Mexico e default",
|
||||
},
|
||||
() => {
|
||||
const mexicoSql = fetchModule.buildManagerResourceWorkloadSql({
|
||||
fromDateSql: "DATE '2026-05-01'",
|
||||
toDateSql: "DATE '2026-05-31'",
|
||||
cluster: "Mexico",
|
||||
});
|
||||
assert.match(mexicoSql, /"Cluster" = 'Mexico'/);
|
||||
|
||||
const defaultSql = fetchModule.buildManagerResourceWorkloadSql({
|
||||
fromDateSql: "DATE '2026-05-01'",
|
||||
toDateSql: "DATE '2026-05-31'",
|
||||
});
|
||||
assert.match(defaultSql, /"Cluster" = 'Brazil'/);
|
||||
|
||||
return "manager/resource workload cluster is explicit when provided and Brazil by default";
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"coverage-limit-cap",
|
||||
"coverage enrichment limit never exceeds Oracle DV maximum",
|
||||
{
|
||||
entrada: "resolveCoverageLimit com limites baixo, normal e alto",
|
||||
},
|
||||
() => {
|
||||
assert.equal(fetchModule.resolveCoverageLimit(10), 5000);
|
||||
assert.equal(fetchModule.resolveCoverageLimit(1000), 8000);
|
||||
assert.equal(fetchModule.resolveCoverageLimit(5000), 10000);
|
||||
assert.equal(fetchModule.resolveCoverageLimit(10000), 10000);
|
||||
|
||||
return "coverage limit is bounded to 5,000..10,000";
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"current-workload-defaults",
|
||||
"current workload defaults are documented and applied locally",
|
||||
{
|
||||
entrada: "applyCurrentWorkloadIntentDefaults para owner atual e month explícito",
|
||||
},
|
||||
() => {
|
||||
assert.equal(catalogSaysCurrentWorkloadDefaults(catalogs, "workloads-by-owner-current"), true);
|
||||
assert.equal(catalogSaysCurrentWorkloadDefaults(catalogs, "workloads-by-owner-manager-current"), true);
|
||||
const expectedDefaults = parseCurrentWorkloadDefaults(catalogs, "workloads-by-owner-current");
|
||||
|
||||
const defaults = fetchModule.applyCurrentWorkloadIntentDefaults({
|
||||
intent: "workloads-by-owner-current",
|
||||
"owner-email": "territory.owner@oracle.com",
|
||||
});
|
||||
assert.equal(defaults.quarter, expectedDefaults.quarter);
|
||||
assert.equal(defaults.solution, expectedDefaults.solution);
|
||||
assert.equal(defaults["revenue-type-group"], expectedDefaults.revenueTypeGroup);
|
||||
assert.equal(fetchModule.hasAnyDateWindowArgs(defaults), true);
|
||||
|
||||
const explicitMonth = fetchModule.applyCurrentWorkloadIntentDefaults({
|
||||
month: "2026-04",
|
||||
"owner-email": "territory.owner@oracle.com",
|
||||
});
|
||||
assert.equal(explicitMonth.month, "2026-04");
|
||||
assert.equal(explicitMonth.quarter, undefined);
|
||||
assert.equal(explicitMonth.solution, expectedDefaults.solution);
|
||||
return `quarter=${defaults.quarter}; solution=${defaults.solution}; revenueTypeGroup=${defaults["revenue-type-group"]}; explicitMonth preserved`;
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"sa-attach-window-sql",
|
||||
"SA Attach window SQL uses the documented revenue-line close date field",
|
||||
{
|
||||
entrada: "buildSaAttachOpportunityWhereClauses com janela 2026-03-01..2026-05-31",
|
||||
},
|
||||
() => {
|
||||
assert.equal(catalogSaysUseRevenueLineCloseDate(catalogs), true);
|
||||
|
||||
const defaults = parseSaAttachDefaults(catalogs.initialContext);
|
||||
const dateField = parseSaAttachDateWindowField(catalogs);
|
||||
const dataset = parsePrimaryCommercialSubjectArea(catalogs.apiMap);
|
||||
const clauses = fetchModule.buildSaAttachOpportunityWhereClauses({
|
||||
dataset,
|
||||
fiscalYear: null,
|
||||
previousFiscalYear: null,
|
||||
fromDateSql: "DATE '2026-03-01'",
|
||||
toDateSql: "DATE '2026-05-31'",
|
||||
cluster: defaults.cluster[0],
|
||||
solutions: [defaults.solution[0]],
|
||||
statuses: defaults.revenueLineStatus,
|
||||
productLobs: [defaults.executiveProductLob[0]],
|
||||
revenueTypeGroups: [defaults.revenueTypeGroup.at(-1)],
|
||||
salesCreditType: defaults.salesCreditType[0],
|
||||
minOpportunityProbability: Number(defaults.minOpportunityProbability[0]),
|
||||
ownerEmail: "territory.owner@oracle.com",
|
||||
ownerManagerEmail: "territory.manager@oracle.com",
|
||||
forecastTypes: ["Forecast"],
|
||||
});
|
||||
const sql = clauses.join("\n");
|
||||
|
||||
assert.match(sql, new RegExp(escapeRegExp(dateField)));
|
||||
assert.match(sql, /DATE '2026-03-01'/);
|
||||
assert.match(sql, /DATE '2026-05-31'/);
|
||||
assert.match(sql, /Level 7 Territory Owner E-mail/);
|
||||
assert.match(sql, /Revenue Line Forecast Type Group/);
|
||||
assert.doesNotMatch(sql, /Revenue Line Close Year/);
|
||||
return `field=${dateField}; SQL inclui forecast/owner e não usa Revenue Line Close Year`;
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"scope-sql",
|
||||
"scope-related SQL follows catalog-backed org-wide and my-team rules",
|
||||
{
|
||||
entrada: "renderViewSql para Presales Aux, Hours e srs-by-resource-email",
|
||||
},
|
||||
() => {
|
||||
const coverageOrg = fetchModule.renderViewSql("sa-attach-presales-coverage");
|
||||
const coverageTeam = fetchModule.renderViewSql("sa-attach-presales-coverage", { scope: "my-team" });
|
||||
const hoursOrg = fetchModule.renderViewSql("sa-attach-hours-by-opportunity");
|
||||
const hoursTeam = fetchModule.renderViewSql("sa-attach-hours-by-opportunity", { scope: "my-team" });
|
||||
const srTeam = fetchModule.renderViewSql("srs-by-resource-email", {
|
||||
resourceEmailSql: "'resource.member@oracle.com'",
|
||||
matchRole: "team",
|
||||
srStatuses: [],
|
||||
});
|
||||
const srLead = fetchModule.renderViewSql("srs-by-resource-email", {
|
||||
resourceEmailSql: "'resource.member@oracle.com'",
|
||||
matchRole: "lead",
|
||||
srStatuses: [],
|
||||
});
|
||||
|
||||
assert.match(catalogs.skill, /Presales Involvement Aux - V1.*org-wide/s);
|
||||
assert.doesNotMatch(coverageOrg, /SEC_ARIA_SI-SR-TEAM/);
|
||||
assert.match(coverageTeam, /SEC_ARIA_SI-SR-TEAM/);
|
||||
assert.doesNotMatch(hoursOrg, /SEC_ARIA_SI-SR-TEAM/);
|
||||
assert.match(hoursTeam, /SEC_ARIA_SI-SR-TEAM/);
|
||||
assert.match(srTeam, /SE Team Level 1 Email Address/);
|
||||
assert.match(srTeam, /SE Team Level 4 Email Address/);
|
||||
assert.match(srLead, /"Service Request"\."Lead Email" = 'resource.member@oracle.com'/);
|
||||
return "org-wide sem SEC_ARIA; my-team com SEC_ARIA; SR team usa levels 1..4; lead só quando explícito";
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"current-team-summary",
|
||||
"current-team coverage summaries preserve unmatched commercial rows",
|
||||
{
|
||||
entrada: "summarizeCurrentTeamOpportunityCoverage com 2 oportunidades e 1 cobertura",
|
||||
},
|
||||
() => {
|
||||
assert.equal(catalogSaysPreserveMixedRows(catalogs), true);
|
||||
|
||||
const result = fetchModule.summarizeCurrentTeamOpportunityCoverage(
|
||||
[
|
||||
{
|
||||
opportunityId: "OPP-1",
|
||||
opportunityName: "Alpha",
|
||||
customerName: "Acme",
|
||||
opportunityStatus: "Open",
|
||||
closeDate: "2026-03-31",
|
||||
salesStage: "Develop",
|
||||
bookingValue: 100000,
|
||||
workloadAmount: 50000,
|
||||
},
|
||||
{
|
||||
opportunityId: "OPP-2",
|
||||
opportunityName: "Beta",
|
||||
customerName: "Bravo",
|
||||
opportunityStatus: "Open",
|
||||
closeDate: "2026-04-01",
|
||||
salesStage: "Close",
|
||||
bookingValue: 200000,
|
||||
workloadAmount: 75000,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
opportunityId: "OPP-1",
|
||||
seTeamLevel5EmailAddress: "engineer.one@oracle.com",
|
||||
opportunityHoursAttribute: 6,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
assert.equal(result.records.length, 2);
|
||||
assert.equal(result.summary.totalWindowOpportunities, 2);
|
||||
assert.equal(result.summary.matchedOpportunities, 1);
|
||||
assert.equal(result.summary.unmatchedOpportunities, 1);
|
||||
assert.equal(result.summary.totalBookingValue, 300000);
|
||||
assert.equal(result.summary.totalWorkloadAmount, 125000);
|
||||
|
||||
const unmatched = result.records.find((record) => record.opportunityId === "OPP-2");
|
||||
assert.deepEqual(unmatched.teamMemberEmails, []);
|
||||
assert.equal(unmatched.coverageRows, 0);
|
||||
return `records=${result.records.length}; matched=${result.summary.matchedOpportunities}; unmatched=${result.summary.unmatchedOpportunities}; totalWorkload=${result.summary.totalWorkloadAmount}`;
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"manager-resource-summary",
|
||||
"manager-resource workload summaries keep resource involvement details",
|
||||
{
|
||||
entrada: "summarizeOrgWideRevenueLineCoverage com cobertura Aux e cenário opcional com SR injetado",
|
||||
},
|
||||
() => {
|
||||
const defaultResult = fetchModule.summarizeOrgWideRevenueLineCoverage(
|
||||
[
|
||||
{
|
||||
closeDate: "2026-04-06",
|
||||
revenueLineId: "RL-1",
|
||||
opportunityId: "OPP-1",
|
||||
opportunityName: "Alpha",
|
||||
customerName: "Acme",
|
||||
workloadAmount: 240000,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
opportunityId: "OPP-1",
|
||||
seTeamLevel4EmailAddress: "resource.manager.a@oracle.com",
|
||||
seTeamLevel5EmailAddress: "resource.member.a@oracle.com",
|
||||
},
|
||||
{
|
||||
opportunityId: "OPP-1",
|
||||
seTeamLevel4EmailAddress: "resource.manager.b@oracle.com",
|
||||
seTeamLevel5EmailAddress: "resource.member.b@oracle.com",
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
assert.deepEqual(defaultResult.records[0].resourceDetails, [
|
||||
{
|
||||
resourceEmail: "resource.member.a@oracle.com",
|
||||
companionEmails: ["resource.member.b@oracle.com"],
|
||||
},
|
||||
{
|
||||
resourceEmail: "resource.member.b@oracle.com",
|
||||
companionEmails: ["resource.member.a@oracle.com"],
|
||||
},
|
||||
]);
|
||||
assert.equal(Object.hasOwn(defaultResult.records[0].resourceDetails[0], "srNumbers"), false);
|
||||
|
||||
const result = fetchModule.summarizeOrgWideRevenueLineCoverage(
|
||||
[
|
||||
{
|
||||
closeDate: "2026-04-06",
|
||||
revenueLineId: "RL-1",
|
||||
opportunityId: "OPP-1",
|
||||
opportunityName: "Alpha",
|
||||
customerName: "Acme",
|
||||
workloadAmount: 240000,
|
||||
},
|
||||
{
|
||||
closeDate: "2026-04-07",
|
||||
revenueLineId: "RL-2",
|
||||
opportunityId: "OPP-2",
|
||||
opportunityName: "Beta",
|
||||
customerName: "Bravo",
|
||||
workloadAmount: 367000,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
opportunityId: "OPP-1",
|
||||
seTeamLevel4EmailAddress: "resource.manager.a@oracle.com",
|
||||
seTeamLevel5EmailAddress: "resource.member.a@oracle.com",
|
||||
},
|
||||
{
|
||||
opportunityId: "OPP-1",
|
||||
seTeamLevel4EmailAddress: "resource.manager.b@oracle.com",
|
||||
seTeamLevel5EmailAddress: "resource.member.b@oracle.com",
|
||||
},
|
||||
],
|
||||
new Map([["OPP-1::resource.member.a@oracle.com", new Set(["SR-100"])] ]),
|
||||
new Map([
|
||||
[
|
||||
"resource.member.a@oracle.com",
|
||||
new Map([
|
||||
["SR-900", { srNumber: "SR-900", customerName: "Acme", title: "Internal follow-up" }],
|
||||
["SR-901", { srNumber: "SR-901", customerName: "Other Corp", title: "Ignore me" }],
|
||||
]),
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
assert.equal(result.records.length, 1);
|
||||
assert.equal(result.records[0].revenueLineId, "RL-1");
|
||||
assert.deepEqual(result.records[0].managerEmails, [
|
||||
"resource.manager.a@oracle.com",
|
||||
"resource.manager.b@oracle.com",
|
||||
]);
|
||||
assert.deepEqual(result.records[0].resourceEmails, [
|
||||
"resource.member.a@oracle.com",
|
||||
"resource.member.b@oracle.com",
|
||||
]);
|
||||
|
||||
const resourceA = result.records[0].resourceDetails.find(
|
||||
(detail) => detail.resourceEmail === "resource.member.a@oracle.com"
|
||||
);
|
||||
assert.deepEqual(resourceA.srNumbers, ["SR-100", "SR-900"]);
|
||||
assert.deepEqual(resourceA.directSrNumbers, ["SR-100"]);
|
||||
assert.deepEqual(resourceA.candidateInternalSrNumbers, ["SR-900"]);
|
||||
assert.equal(resourceA.srLinkageMode, "direct-plus-candidate-internal");
|
||||
assert.deepEqual(resourceA.companionEmails, ["resource.member.b@oracle.com"]);
|
||||
|
||||
assert.equal(result.summary.totalWindowRevenueLines, 2);
|
||||
assert.equal(result.summary.matchedRevenueLines, 1);
|
||||
assert.equal(result.summary.matchedOpportunities, 1);
|
||||
assert.equal(result.summary.totalWorkloadAmount, 240000);
|
||||
assert.equal(result.summary.resourcesMatched, 2);
|
||||
return `defaultResourceDetails=${defaultResult.records[0].resourceDetails.length}; defaultSrFields=omitted; optionalSrLinkage=${resourceA.srLinkageMode}; resourcesMatched=${result.summary.resourcesMatched}`;
|
||||
});
|
||||
|
||||
functionTest(
|
||||
"api-metrics",
|
||||
"API metrics count raw backend rows and ignore local-cache-only rows",
|
||||
{
|
||||
entrada: "buildApiMetrics com rows 3 + 2 e cache local não contado",
|
||||
},
|
||||
() => {
|
||||
assert.equal(catalogSaysApiMetricsAreRawRows(catalogs), true);
|
||||
|
||||
const metrics = fetchModule.buildApiMetrics([
|
||||
{
|
||||
label: "window",
|
||||
result: { aResults: [[1], [2], [3]] },
|
||||
},
|
||||
{
|
||||
label: "coverage",
|
||||
result: { aResults: [[1], [2]] },
|
||||
},
|
||||
{
|
||||
label: "sessionMetaCache",
|
||||
rowsReturned: 0,
|
||||
source: "session-meta-cache",
|
||||
countedInTotal: false,
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(metrics.totalRowsPulledFromApi, 5);
|
||||
assert.equal(metrics.queries.length, 3);
|
||||
assert.equal(metrics.explanation, "Raw rows pulled from API: 5.");
|
||||
return `totalRowsPulledFromApi=${metrics.totalRowsPulledFromApi}; queries=${metrics.queries.length}; local-cache ignored`;
|
||||
});
|
||||
|
||||
function formatFunctionResultsTable(rows) {
|
||||
const header = ["id", "teste", "entrada", "saida", "avaliacao"];
|
||||
const lines = [
|
||||
`| ${header.join(" | ")} |`,
|
||||
`| ${header.map(() => "---").join(" | ")} |`,
|
||||
...rows.map((row) =>
|
||||
`| ${markdownCell(row.id)} | ${markdownCell(row.teste)} | ${markdownCell(row.entrada)} | ${markdownCell(row.saida)} | ${markdownCell(row.avaliacao)} |`
|
||||
),
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function markdownCell(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/\r?\n/g, "<br>")
|
||||
.replace(/\|/g, "\\|");
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
190
tests/helpers/catalogs.mjs
Normal file
190
tests/helpers/catalogs.mjs
Normal file
@@ -0,0 +1,190 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { skillHome } from "./cli.mjs";
|
||||
|
||||
export async function readSkillCatalogs() {
|
||||
const [skill, scriptCatalog, initialContext, apiMap] = await Promise.all([
|
||||
fs.readFile(path.join(skillHome, "SKILL.md"), "utf8"),
|
||||
fs.readFile(path.join(skillHome, "references", "script-catalog.md"), "utf8"),
|
||||
fs.readFile(path.join(skillHome, "references", "initial-context-catalog.md"), "utf8"),
|
||||
fs.readFile(path.join(skillHome, "references", "api-map.md"), "utf8"),
|
||||
]);
|
||||
return {
|
||||
skill,
|
||||
scriptCatalog,
|
||||
initialContext,
|
||||
apiMap,
|
||||
allText: [skill, scriptCatalog, initialContext, apiMap].join("\n"),
|
||||
};
|
||||
}
|
||||
|
||||
export function documentedFlags(catalogs) {
|
||||
const flags = new Set();
|
||||
const regex = /`(--[a-z0-9-]+)(?:\s+[^`]*)?`/gi;
|
||||
let match = regex.exec(catalogs.allText);
|
||||
while (match) {
|
||||
flags.add(match[1]);
|
||||
match = regex.exec(catalogs.allText);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
export function catalogMentionsIntent(catalogs, intentName) {
|
||||
return new RegExp(`\\b${escapeRegExp(intentName)}\\b`).test(catalogs.skill + "\n" + catalogs.scriptCatalog);
|
||||
}
|
||||
|
||||
export function catalogMentionsView(catalogs, viewName) {
|
||||
return new RegExp(`\\b${escapeRegExp(viewName)}\\b`).test(catalogs.skill + "\n" + catalogs.scriptCatalog);
|
||||
}
|
||||
|
||||
export function commandAppearsInCatalog(catalogs, args) {
|
||||
const normalizedCatalog = normalizeWhitespace(catalogs.skill + "\n" + catalogs.scriptCatalog + "\n" + catalogs.apiMap);
|
||||
const normalizedArgs = normalizeWhitespace(args.join(" "));
|
||||
return normalizedCatalog.includes(normalizedArgs);
|
||||
}
|
||||
|
||||
export function parseDefaultWeekYear(scriptCatalog) {
|
||||
const match = /-\s*`week-year\s+(\d{4})`/i.exec(scriptCatalog);
|
||||
if (!match) {
|
||||
throw new Error("Could not parse default week-year from references/script-catalog.md.");
|
||||
}
|
||||
return Number(match[1]);
|
||||
}
|
||||
|
||||
export function parseWeekRow(scriptCatalog, weekLabel) {
|
||||
const normalizedLabel = String(weekLabel).toUpperCase().replace(/^W?/, "W");
|
||||
const regex = new RegExp(`\\|\\s*${escapeRegExp(normalizedLabel)}\\s*\\|\\s*(\\d{4}-\\d{2}-\\d{2})\\s*\\|\\s*(\\d{4}-\\d{2}-\\d{2})\\s*\\|`, "i");
|
||||
const match = regex.exec(scriptCatalog);
|
||||
if (!match) {
|
||||
throw new Error(`Could not parse ${normalizedLabel} from references/script-catalog.md.`);
|
||||
}
|
||||
return {
|
||||
weekLabel: normalizedLabel,
|
||||
fromDate: match[1],
|
||||
toDate: match[2],
|
||||
};
|
||||
}
|
||||
|
||||
export function parseSaAttachDefaults(initialContext) {
|
||||
const line = findLine(initialContext, "SA Attach defaults:");
|
||||
const rawDefaults = line.slice(line.indexOf(":") + 1).split(";").map((part) => part.trim()).filter(Boolean);
|
||||
const defaults = {};
|
||||
for (const part of rawDefaults) {
|
||||
const match = /`--([^=]+)=([^`]+)`/.exec(part);
|
||||
if (!match) continue;
|
||||
defaults[toCamelKey(match[1])] = splitCatalogCsv(match[2]);
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
|
||||
export function parseSaAttachAliases(initialContext) {
|
||||
const line = findLine(initialContext, "SA Attach aliases:");
|
||||
const aliases = {};
|
||||
const rawAliases = line.slice(line.indexOf(":") + 1).split(";").map((part) => part.trim()).filter(Boolean);
|
||||
for (const item of rawAliases) {
|
||||
const match = /`([^`]+)`\s*->\s*`([^`]+)`/.exec(item);
|
||||
if (!match) continue;
|
||||
aliases[match[1].toLowerCase()] = splitCatalogCsv(match[2]);
|
||||
}
|
||||
return aliases;
|
||||
}
|
||||
|
||||
export function catalogSaysCurrentWorkloadDefaults(catalogs, intentName) {
|
||||
try {
|
||||
parseCurrentWorkloadDefaults(catalogs, intentName);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseCurrentWorkloadDefaults(catalogs, intentName) {
|
||||
const source = catalogs.skill + "\n" + catalogs.scriptCatalog;
|
||||
const pattern = new RegExp(
|
||||
`\\\`${escapeRegExp(intentName)}\\\`[\\s\\S]{0,500}?defaults? to \\\`([^\\\`]+)\\\`, \\\`([^\\\`]+)\\\`, and \\\`([^\\\`]+)\\\``,
|
||||
"i"
|
||||
);
|
||||
const match = pattern.exec(source);
|
||||
if (!match) {
|
||||
throw new Error(`Could not parse documented defaults for ${intentName}.`);
|
||||
}
|
||||
return {
|
||||
quarter: match[1],
|
||||
solution: match[2],
|
||||
revenueTypeGroup: match[3],
|
||||
};
|
||||
}
|
||||
|
||||
export function catalogSaysUseRevenueLineCloseDate(catalogs) {
|
||||
try {
|
||||
parseSaAttachDateWindowField(catalogs);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseSaAttachDateWindowField(catalogs) {
|
||||
const match =
|
||||
/date-window flags now constrain `([^`]+)` directly/i.exec(catalogs.skill) ||
|
||||
/date-window flags are applied to `([^`]+)`/i.exec(catalogs.scriptCatalog);
|
||||
if (!match) {
|
||||
throw new Error("Could not parse the SA Attach date-window field from references.");
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
export function parsePrimaryCommercialSubjectArea(apiMap) {
|
||||
const match = /Primary commercial subject area:[\s\S]*?`([^`]+)`/i.exec(apiMap);
|
||||
if (!match) {
|
||||
throw new Error("Could not parse the primary commercial subject area from references/api-map.md.");
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
export function parsePrimaryCommercialSourceName(apiMap) {
|
||||
const subjectArea = parsePrimaryCommercialSubjectArea(apiMap);
|
||||
const match = /\.'([^']+)'\)$/i.exec(subjectArea);
|
||||
if (!match) {
|
||||
throw new Error("Could not parse the primary commercial source name from references/api-map.md.");
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
export function catalogSaysPreserveMixedRows(catalogs) {
|
||||
return /preserve every org-wide opportunity\/workload row/i.test(catalogs.skill);
|
||||
}
|
||||
|
||||
export function catalogSaysApiMetricsAreRawRows(catalogs) {
|
||||
return /apiMetrics\.totalRowsPulledFromApi/i.test(catalogs.skill)
|
||||
&& /Do not add local-cache-only rows/i.test(catalogs.skill);
|
||||
}
|
||||
|
||||
export function splitCatalogCsv(value) {
|
||||
return String(value || "")
|
||||
.split(",")
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function findLine(text, marker) {
|
||||
const line = String(text)
|
||||
.split(/\r?\n/)
|
||||
.find((candidate) => candidate.includes(marker));
|
||||
if (!line) {
|
||||
throw new Error(`Could not find "${marker}" in catalog text.`);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
function toCamelKey(flagName) {
|
||||
return flagName.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
||||
}
|
||||
|
||||
function normalizeWhitespace(value) {
|
||||
return String(value).replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
168
tests/helpers/cli.mjs
Normal file
168
tests/helpers/cli.mjs
Normal file
@@ -0,0 +1,168 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const helperDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export const testsDir = path.resolve(helperDir, "..");
|
||||
export const skillHome = path.resolve(testsDir, "..");
|
||||
export const fetchScriptPath = path.join(skillHome, "scripts", "fetch-sales-intelligence.mjs");
|
||||
|
||||
let preparedRuntimeHomePromise = null;
|
||||
|
||||
export function testRuntimeHome() {
|
||||
return (
|
||||
process.env.ORACLE_SI_TEST_RUNTIME_HOME ||
|
||||
path.join(os.tmpdir(), "knowledge-one-view-cli-tests", "runtime")
|
||||
);
|
||||
}
|
||||
|
||||
async function prepareRuntimeHome() {
|
||||
if (process.env.ORACLE_SI_TEST_RUNTIME_HOME) {
|
||||
await fs.mkdir(process.env.ORACLE_SI_TEST_RUNTIME_HOME, { recursive: true });
|
||||
return process.env.ORACLE_SI_TEST_RUNTIME_HOME;
|
||||
}
|
||||
|
||||
const runtimeHome = testRuntimeHome();
|
||||
if (!process.env.ORACLE_SI_RUNTIME_HOME) {
|
||||
await fs.mkdir(runtimeHome, { recursive: true });
|
||||
return runtimeHome;
|
||||
}
|
||||
|
||||
if (!preparedRuntimeHomePromise) {
|
||||
preparedRuntimeHomePromise = (async () => {
|
||||
await fs.rm(runtimeHome, { recursive: true, force: true });
|
||||
await fs.mkdir(runtimeHome, { recursive: true });
|
||||
for (const fileName of ["auth-state.json", "session-meta.json"]) {
|
||||
await fs.copyFile(
|
||||
path.join(process.env.ORACLE_SI_RUNTIME_HOME, fileName),
|
||||
path.join(runtimeHome, fileName)
|
||||
);
|
||||
}
|
||||
return runtimeHome;
|
||||
})();
|
||||
}
|
||||
|
||||
return preparedRuntimeHomePromise;
|
||||
}
|
||||
|
||||
export function flagsInArgs(args) {
|
||||
return args.filter((arg) => String(arg).startsWith("--"));
|
||||
}
|
||||
|
||||
export function argValue(args, flagName) {
|
||||
const index = args.indexOf(flagName);
|
||||
return index >= 0 ? args[index + 1] : null;
|
||||
}
|
||||
|
||||
export function executionModes(args) {
|
||||
return ["--intent", "--view", "--query", "--sql"].filter((flagName) => args.includes(flagName));
|
||||
}
|
||||
|
||||
export function withoutFlags(args, flagsToRemove) {
|
||||
const flags = new Set(flagsToRemove);
|
||||
const next = [];
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const token = args[index];
|
||||
if (!flags.has(token)) {
|
||||
next.push(token);
|
||||
continue;
|
||||
}
|
||||
if (token !== "--query" && token !== "--render-sql" && args[index + 1] && !args[index + 1].startsWith("--")) {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
export async function runFetch(cliArgs, options = {}) {
|
||||
const {
|
||||
expectCode = 0,
|
||||
timeoutMs = 120000,
|
||||
runtimeHome: requestedRuntimeHome = null,
|
||||
} = options;
|
||||
const runtimeHome = requestedRuntimeHome || (await prepareRuntimeHome());
|
||||
const noCacheArgs = cliArgs.includes("--no-cache") ? [...cliArgs] : [...cliArgs, "--no-cache"];
|
||||
const finalArgs = noCacheArgs.includes("--runtime-home")
|
||||
? noCacheArgs
|
||||
: [...noCacheArgs, "--runtime-home", runtimeHome];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, [fetchScriptPath, ...finalArgs], {
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: process.env,
|
||||
});
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
let timedOut = false;
|
||||
const startedAt = process.hrtime.bigint();
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
child.kill();
|
||||
}, timeoutMs);
|
||||
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stderr.setEncoding("utf8");
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk;
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += chunk;
|
||||
});
|
||||
|
||||
child.on("error", reject);
|
||||
child.on("close", (code) => {
|
||||
clearTimeout(timer);
|
||||
const durationMs = Number(process.hrtime.bigint() - startedAt) / 1_000_000;
|
||||
let json = null;
|
||||
try {
|
||||
json = stdout.trim() ? JSON.parse(stdout) : null;
|
||||
} catch {}
|
||||
|
||||
const result = {
|
||||
args: finalArgs,
|
||||
code,
|
||||
stdout,
|
||||
stderr,
|
||||
json,
|
||||
timedOut,
|
||||
durationMs,
|
||||
};
|
||||
|
||||
if (expectCode !== null && code !== expectCode) {
|
||||
reject(
|
||||
new Error(
|
||||
[
|
||||
`Unexpected exit code ${code}; expected ${expectCode}.`,
|
||||
`Command: ${process.execPath} ${fetchScriptPath} ${finalArgs.join(" ")}`,
|
||||
stderr.trim() || stdout.trim(),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (expectCode === 0 && (!json || typeof json !== "object")) {
|
||||
reject(
|
||||
new Error(
|
||||
[
|
||||
"Expected JSON output from fetch-sales-intelligence.mjs.",
|
||||
`Command: ${process.execPath} ${fetchScriptPath} ${finalArgs.join(" ")}`,
|
||||
stderr.trim() || stdout.trim(),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
142
tests/helpers/examples.mjs
Normal file
142
tests/helpers/examples.mjs
Normal file
@@ -0,0 +1,142 @@
|
||||
import { runFetch } from "./cli.mjs";
|
||||
|
||||
const EMAIL_REGEX = /^[a-z0-9._%+-]+@oracle\.com$/i;
|
||||
const PLACEHOLDER_REGEX = /\{\{([a-zA-Z0-9]+)\}\}/g;
|
||||
|
||||
export function hasPlaceholders(value) {
|
||||
return PLACEHOLDER_REGEX.test(String(value || ""));
|
||||
}
|
||||
|
||||
export function resolvePlaceholders(value, examples = {}, { allowUnresolved = false } = {}) {
|
||||
return String(value).replace(PLACEHOLDER_REGEX, (match, key) => {
|
||||
if (examples[key]) return examples[key];
|
||||
if (allowUnresolved) return match;
|
||||
throw new Error(`Missing live example value for {{${key}}}.`);
|
||||
});
|
||||
}
|
||||
|
||||
export function resolvePromptCase(item, examples = {}, { allowUnresolved = false } = {}) {
|
||||
return {
|
||||
...item,
|
||||
prompt: resolvePlaceholders(item.prompt, examples, { allowUnresolved }),
|
||||
args: item.args.map((arg) => resolvePlaceholders(arg, examples, { allowUnresolved })),
|
||||
...(item.liveArgs
|
||||
? { liveArgs: item.liveArgs.map((arg) => resolvePlaceholders(arg, examples, { allowUnresolved })) }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildLiveExampleContext(catalogs) {
|
||||
const catalogResourceManagers = emailsFromCatalog(catalogs.initialContext);
|
||||
const [pipelineSample, presalesSample] = await Promise.all([
|
||||
runFetch(["--view", "sa-attach-pipeline-details", "--limit", "500"], { timeoutMs: 180000 }),
|
||||
runFetch(["--view", "sa-attach-presales-coverage", "--limit", "500"], { timeoutMs: 180000 }),
|
||||
]);
|
||||
|
||||
const pipelineRecords = pipelineSample.json.records || [];
|
||||
const presalesRecords = presalesSample.json.records || [];
|
||||
const resourceManagerEmails = uniqueEmails([
|
||||
...catalogResourceManagers,
|
||||
...valuesFromRecords(presalesRecords, ["seTeamLevel4EmailAddress"]),
|
||||
]);
|
||||
const resourceMemberEmails = uniqueEmails(valuesFromRecords(presalesRecords, ["seTeamLevel5EmailAddress"]));
|
||||
const territoryOwnerEmails = uniqueEmails(
|
||||
valuesFromRecords(pipelineRecords, ["level7TerritoryOwnerEmail", "territoryOwnerEmail"])
|
||||
);
|
||||
const territoryManagerEmails = uniqueEmails(
|
||||
valuesFromRecords(pipelineRecords, [
|
||||
"level2TerritoryOwnerEmail",
|
||||
"level3TerritoryOwnerEmail",
|
||||
"level4TerritoryOwnerEmail",
|
||||
"level5TerritoryOwnerEmail",
|
||||
"level6TerritoryOwnerEmail",
|
||||
])
|
||||
);
|
||||
const [resourceManagerEmailA, resourceManagerEmailB] = pickDistinct(
|
||||
resourceManagerEmails,
|
||||
2,
|
||||
"resource manager emails"
|
||||
);
|
||||
|
||||
return {
|
||||
examples: {
|
||||
resourceManagerEmail: pickRandom(resourceManagerEmails, "resource manager email"),
|
||||
resourceManagerEmailA,
|
||||
resourceManagerEmailB,
|
||||
resourceManagerEmailList: `${resourceManagerEmailA},${resourceManagerEmailB}`,
|
||||
territoryOwnerEmail: pickRandom(territoryOwnerEmails, "territory owner email"),
|
||||
territoryManagerEmail: pickRandom(territoryManagerEmails, "territory manager email"),
|
||||
resourceMember: pickRandom(resourceMemberEmails, "resource member email"),
|
||||
},
|
||||
trace: [
|
||||
{
|
||||
script: "fetch-sales-intelligence.mjs",
|
||||
args: pipelineSample.args,
|
||||
result: `ok; records=${pipelineRecords.length}; source=${pipelineSample.json.request?.dataSource || "unknown"}`,
|
||||
profiles: ["territoryOwnerEmail", "territoryManagerEmail"],
|
||||
},
|
||||
{
|
||||
script: "fetch-sales-intelligence.mjs",
|
||||
args: presalesSample.args,
|
||||
result: `ok; records=${presalesRecords.length}; source=${presalesSample.json.request?.dataSource || "unknown"}`,
|
||||
profiles: ["resourceManagerEmail", "resourceManagerEmailA", "resourceManagerEmailB", "resourceMember"],
|
||||
},
|
||||
],
|
||||
poolSizes: {
|
||||
resourceManagerEmails: resourceManagerEmails.length,
|
||||
resourceMemberEmails: resourceMemberEmails.length,
|
||||
territoryOwnerEmails: territoryOwnerEmails.length,
|
||||
territoryManagerEmails: territoryManagerEmails.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function placeholderKeysForItem(item) {
|
||||
const text = [item.prompt, ...(item.args || []), ...(item.liveArgs || [])].join("\n");
|
||||
return Array.from(text.matchAll(PLACEHOLDER_REGEX), (match) => match[1]);
|
||||
}
|
||||
|
||||
function emailsFromCatalog(text) {
|
||||
return uniqueEmails(Array.from(String(text || "").matchAll(/[a-z0-9._%+-]+@oracle\.com/gi), (match) => match[0]));
|
||||
}
|
||||
|
||||
function valuesFromRecords(records, fieldNames) {
|
||||
const values = [];
|
||||
for (const record of records || []) {
|
||||
for (const fieldName of fieldNames) {
|
||||
if (record?.[fieldName]) values.push(record[fieldName]);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function uniqueEmails(values) {
|
||||
return Array.from(
|
||||
new Set(
|
||||
values
|
||||
.map((value) => String(value || "").trim().toLowerCase())
|
||||
.filter((value) => EMAIL_REGEX.test(value))
|
||||
.filter((value) => !/^(resource|territory)\./.test(value))
|
||||
)
|
||||
).sort();
|
||||
}
|
||||
|
||||
function pickRandom(values, label) {
|
||||
if (!values.length) {
|
||||
throw new Error(`Could not resolve any real ${label} from the skill catalogs or live DV sample.`);
|
||||
}
|
||||
return values[Math.floor(Math.random() * values.length)];
|
||||
}
|
||||
|
||||
function pickDistinct(values, count, label) {
|
||||
if (values.length < count) {
|
||||
throw new Error(`Could not resolve ${count} distinct real ${label} from the skill catalogs or live DV sample.`);
|
||||
}
|
||||
const remaining = [...values];
|
||||
const picked = [];
|
||||
while (picked.length < count) {
|
||||
const index = Math.floor(Math.random() * remaining.length);
|
||||
picked.push(remaining.splice(index, 1)[0]);
|
||||
}
|
||||
return picked;
|
||||
}
|
||||
163
tests/lib-contracts.test.mjs
Normal file
163
tests/lib-contracts.test.mjs
Normal file
@@ -0,0 +1,163 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { skillHome } from "./helpers/cli.mjs";
|
||||
|
||||
const cliShared = await import(pathToFileURL(path.join(skillHome, "scripts", "lib", "cli-shared.mjs")).href);
|
||||
const outputContract = await import(pathToFileURL(path.join(skillHome, "scripts", "lib", "output-contract.mjs")).href);
|
||||
const runtimePaths = await import(pathToFileURL(path.join(skillHome, "scripts", "lib", "runtime-paths.mjs")).href);
|
||||
const authShared = await import(pathToFileURL(path.join(skillHome, "scripts", "lib", "auth-shared.mjs")).href);
|
||||
|
||||
test("cli-shared parses flags and csv values for agent-built commands", () => {
|
||||
assert.deepEqual(
|
||||
cliShared.parseArgs([
|
||||
"ignored-position",
|
||||
"--query",
|
||||
"--select",
|
||||
"opportunityId,",
|
||||
"customerName",
|
||||
"--render-sql",
|
||||
"--limit",
|
||||
"10",
|
||||
]),
|
||||
{
|
||||
query: "true",
|
||||
select: "opportunityId, customerName",
|
||||
"render-sql": "true",
|
||||
limit: "10",
|
||||
}
|
||||
);
|
||||
|
||||
const fallback = ["default"];
|
||||
assert.equal(cliShared.splitCsv("", fallback), fallback);
|
||||
assert.deepEqual(cliShared.splitCsv(" opportunityId, , customerName , bookingValue "), [
|
||||
"opportunityId",
|
||||
"customerName",
|
||||
"bookingValue",
|
||||
]);
|
||||
});
|
||||
|
||||
test("output-contract formats display money while preserving API metrics defaults", () => {
|
||||
const payload = outputContract.preparePayloadForOutput({
|
||||
columns: ["opportunityId", "bookingValue", "workloadAmount"],
|
||||
request: {
|
||||
query: {
|
||||
measure: [{ fieldName: "bookingValue", outputName: "totalBooking" }],
|
||||
},
|
||||
},
|
||||
records: [
|
||||
{
|
||||
opportunityId: "OPP-1",
|
||||
bookingValue: 1234567,
|
||||
workloadAmount: "90,000",
|
||||
totalBooking: 5000,
|
||||
note: "unchanged",
|
||||
},
|
||||
],
|
||||
summary: {
|
||||
totalBookingValue: 1234567,
|
||||
nested: { workloadAmount: 23000 },
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(payload.records[0].bookingValue, "1.234k");
|
||||
assert.equal(payload.records[0].workloadAmount, "90k");
|
||||
assert.equal(payload.records[0].totalBooking, "5k");
|
||||
assert.equal(payload.records[0].note, "unchanged");
|
||||
assert.equal(payload.summary.totalBookingValue, "1.234k");
|
||||
assert.equal(payload.summary.nested.workloadAmount, "23k");
|
||||
assert.equal(payload.apiMetrics.totalRowsPulledFromApi, 0);
|
||||
});
|
||||
|
||||
test("output-contract normalizes Oracle cells and warnings", () => {
|
||||
assert.equal(outputContract.normalizeCell("02-MAY-26"), "2026-05-02");
|
||||
assert.equal(outputContract.normalizeCell("31-DEC-99"), "1999-12-31");
|
||||
assert.equal(outputContract.normalizeCell("__CODEX_NULL__"), null);
|
||||
assert.equal(outputContract.normalizeCell(undefined), null);
|
||||
|
||||
assert.deepEqual(
|
||||
outputContract.mapRows(["closeDate", "amount"], [["02-MAY-2026", "__CODEX_NULL__"]]),
|
||||
[{ closeDate: "2026-05-02", amount: null }]
|
||||
);
|
||||
|
||||
const warnings = outputContract.buildWarnings({
|
||||
requestedLimit: 10,
|
||||
recordsCount: 10,
|
||||
customSql: true,
|
||||
customSqlColumnsResolved: false,
|
||||
});
|
||||
assert.equal(warnings.length, 2);
|
||||
assert.match(warnings[0], /matches the requested limit/);
|
||||
assert.match(warnings[1], /Custom SQL records are returned as raw row arrays/);
|
||||
});
|
||||
|
||||
test("runtime-paths routes final and temporary outputs under the workspace skill folder", () => {
|
||||
const workspaceRoot = path.join("/tmp", "kov-runtime-contracts");
|
||||
const runtimeHome = path.join(workspaceRoot, "knowledge-one-view-cli", "runtime-test");
|
||||
const normalizedRuntimeHome = path.join(workspaceRoot, "knowledge-one-view-cli", "runtime");
|
||||
|
||||
assert.equal(runtimePaths.resolveRuntimeHome(runtimeHome), normalizedRuntimeHome);
|
||||
assert.equal(
|
||||
runtimePaths.resolveOutputFilePath("final.json", runtimeHome),
|
||||
path.join(workspaceRoot, "knowledge-one-view-cli", "output", "final.json")
|
||||
);
|
||||
assert.equal(
|
||||
runtimePaths.resolveOutputFilePath("tmp-render.json", runtimeHome),
|
||||
path.join(workspaceRoot, "knowledge-one-view-cli", "tmp", "tmp-render.json")
|
||||
);
|
||||
assert.equal(
|
||||
runtimePaths.resolveOutputFilePath("reports/final.json", runtimeHome),
|
||||
path.join(workspaceRoot, "knowledge-one-view-cli", "reports", "final.json")
|
||||
);
|
||||
});
|
||||
|
||||
test("runtime-paths refuses to infer a workspace inside .codex", () => {
|
||||
assert.throws(
|
||||
() => runtimePaths.resolveWorkspaceRoot(path.join(runtimePaths.CODEX_HOME, "skills", "knowledge-one-view-cli")),
|
||||
/Cannot infer a workspace root/
|
||||
);
|
||||
});
|
||||
|
||||
test("auth-shared builds cookie headers with domain, path, secure, and expiry rules", () => {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const header = authShared.cookieHeaderFor("https://salesintelligence-dv.oracle.com/analytics/saw.dll", [
|
||||
{ name: "root", value: "1", domain: ".oracle.com", path: "/", secure: true, expires: -1 },
|
||||
{ name: "deep", value: "2", domain: "salesintelligence-dv.oracle.com", path: "/analytics", secure: true, expires: now + 60 },
|
||||
{ name: "expired", value: "3", domain: "oracle.com", path: "/", secure: true, expires: now - 60 },
|
||||
{ name: "httpOnly", value: "4", domain: "oracle.com", path: "/", secure: true, expires: -1 },
|
||||
{ name: "wrong", value: "5", domain: "example.com", path: "/", secure: true, expires: -1 },
|
||||
{ name: "insecureOnly", value: "6", domain: "oracle.com", path: "/", secure: false, expires: -1 },
|
||||
]);
|
||||
|
||||
assert.equal(header, "deep=2; root=1; httpOnly=4; insecureOnly=6");
|
||||
});
|
||||
|
||||
test("auth-shared splits and merges Set-Cookie headers", () => {
|
||||
assert.deepEqual(
|
||||
authShared.splitSetCookieHeader("a=1; Expires=Wed, 21 Oct 2030 07:28:00 GMT; Path=/, b=2; Path=/analytics; HttpOnly; SameSite=None; Secure"),
|
||||
[
|
||||
"a=1; Expires=Wed, 21 Oct 2030 07:28:00 GMT; Path=/",
|
||||
"b=2; Path=/analytics; HttpOnly; SameSite=None; Secure",
|
||||
]
|
||||
);
|
||||
|
||||
const state = { cookies: [{ name: "a", value: "old", domain: "oracle.com", path: "/", expires: -1 }] };
|
||||
const changed = authShared.mergeCookiesFromHeader(
|
||||
state,
|
||||
"https://salesintelligence-dv.oracle.com/analytics/saw.dll",
|
||||
"a=1; Domain=.oracle.com; Path=/, b=2; Path=/analytics; HttpOnly; SameSite=None; Secure"
|
||||
);
|
||||
|
||||
assert.equal(changed, true);
|
||||
assert.equal(state.cookies.length, 2);
|
||||
assert.equal(state.cookies.find((cookie) => cookie.name === "a").value, "1");
|
||||
assert.equal(state.cookies.find((cookie) => cookie.name === "b").sameSite, "None");
|
||||
assert.equal(state.cookies.find((cookie) => cookie.name === "b").httpOnly, true);
|
||||
});
|
||||
|
||||
test("auth-shared classifies refresh-required errors", () => {
|
||||
assert.equal(authShared.isAuthRefreshRequiredError(new Error("Request redirected to Oracle sign-in")), true);
|
||||
assert.equal(authShared.isAuthRefreshRequiredError(new Error("Could not find auth state at /tmp/auth-state.json")), true);
|
||||
assert.equal(authShared.isAuthRefreshRequiredError(new Error("Unsupported --limit abc")), false);
|
||||
});
|
||||
93
tests/prompts/sales-prompts.json
Normal file
93
tests/prompts/sales-prompts.json
Normal file
@@ -0,0 +1,93 @@
|
||||
[
|
||||
{
|
||||
"id": "generic-pipeline",
|
||||
"prompt": "Me traga o pipeline de oportunidades no Knowledge One View.",
|
||||
"catalogRefs": ["SKILL.md"],
|
||||
"args": ["--intent", "opportunities-default", "--limit", "20"],
|
||||
"liveArgs": ["--intent", "opportunities-default", "--limit", "1000"],
|
||||
"liveExpect": {
|
||||
"scopeMode": "org-wide",
|
||||
"dataSource": "primaryCommercial",
|
||||
"allowFallback": false,
|
||||
"minRecords": 100,
|
||||
"minApiRows": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "exact-close-date",
|
||||
"prompt": "Quais oportunidades fecham em 2026-05-02?",
|
||||
"catalogRefs": ["SKILL.md"],
|
||||
"args": ["--intent", "opportunities-close-date", "--date", "2026-05-02", "--limit", "10"],
|
||||
"liveArgs": ["--intent", "opportunities-close-date", "--date", "2026-05-02", "--limit", "1000"],
|
||||
"liveExpect": {
|
||||
"scopeMode": "org-wide",
|
||||
"dataSource": "primaryCommercial",
|
||||
"allowFallback": false,
|
||||
"minRecords": 1,
|
||||
"minApiRows": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "calendar-month",
|
||||
"prompt": "Quais oportunidades fecham em abril de 2026?",
|
||||
"catalogRefs": ["SKILL.md"],
|
||||
"args": ["--intent", "opportunities-close-month", "--month", "2026-04", "--limit", "10"],
|
||||
"liveArgs": ["--intent", "opportunities-close-month", "--month", "2026-04", "--limit", "1000"],
|
||||
"liveExpect": {
|
||||
"scopeMode": "org-wide",
|
||||
"dataSource": "primaryCommercial",
|
||||
"allowFallback": false,
|
||||
"minRecords": 11,
|
||||
"minApiRows": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "manager-week",
|
||||
"prompt": "Quais oportunidades do manager {{resourceManagerEmail}} fecham na week 43?",
|
||||
"catalogRefs": ["SKILL.md", "references/initial-context-catalog.md", "references/script-catalog.md#week-reference"],
|
||||
"args": ["--intent", "opportunities-by-manager-close-window", "--manager-email", "{{resourceManagerEmail}}", "--week", "w43", "--week-year", "catalog-default", "--limit", "10"]
|
||||
},
|
||||
{
|
||||
"id": "owner-workload-current",
|
||||
"prompt": "Me mostre os workloads em forecast agora do vendedor {{territoryOwnerEmail}}.",
|
||||
"catalogRefs": ["SKILL.md"],
|
||||
"args": ["--intent", "workloads-by-owner-current", "--owner-email", "{{territoryOwnerEmail}}", "--forecast-type", "Forecast", "--limit", "10"]
|
||||
},
|
||||
{
|
||||
"id": "owner-manager-workload-current",
|
||||
"prompt": "Quais workloads em forecast agora dos owners abaixo de {{territoryManagerEmail}}?",
|
||||
"catalogRefs": ["SKILL.md", "references/initial-context-catalog.md"],
|
||||
"args": ["--intent", "workloads-by-owner-manager-current", "--owner-manager-email", "{{territoryManagerEmail}}", "--forecast-type", "Forecast", "--limit", "10"]
|
||||
},
|
||||
{
|
||||
"id": "manager-resource-next-7-days",
|
||||
"prompt": "Workloads com close date nos proximos 7 dias em oportunidades com recursos abaixo de {{resourceManagerEmailA}} e {{resourceManagerEmailB}}.",
|
||||
"catalogRefs": ["SKILL.md", "references/initial-context-catalog.md"],
|
||||
"args": ["--intent", "workloads-by-manager-resource-close-window", "--manager-email-list", "{{resourceManagerEmailList}}", "--from-date", "today", "--to-date", "+7d", "--limit", "10"],
|
||||
"liveExpect": {
|
||||
"scopeMode": "org-wide",
|
||||
"dataSource": "primaryCommercial",
|
||||
"allowFallback": false,
|
||||
"noDvSeTeamQueries": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resource-srs",
|
||||
"prompt": "Quais SRs estao com {{resourceMember}} agora?",
|
||||
"catalogRefs": ["SKILL.md"],
|
||||
"args": ["--intent", "srs-by-resource", "--resource", "{{resourceMember}}", "--limit", "10"]
|
||||
},
|
||||
{
|
||||
"id": "current-team-close-window",
|
||||
"prompt": "Quais oportunidades fecham nos proximos 4 dias e tem recursos do meu time?",
|
||||
"catalogRefs": ["SKILL.md"],
|
||||
"args": ["--intent", "opportunities-current-team-close-window", "--from-date", "today", "--to-date", "+4d", "--limit", "10"]
|
||||
},
|
||||
{
|
||||
"id": "ad-hoc-query",
|
||||
"prompt": "Quero opportunityId, cliente, close date, booking e workload das oportunidades de 2026-05-02.",
|
||||
"catalogRefs": ["SKILL.md"],
|
||||
"args": ["--query", "--select", "opportunityId,customerName,closeDate,bookingValue,workloadAmount", "--where", "closeDate = DATE '2026-05-02'", "--render-sql"],
|
||||
"live": false
|
||||
}
|
||||
]
|
||||
21
tests/run.mjs
Normal file
21
tests/run.mjs
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const testsDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const testFiles = [
|
||||
path.join(testsDir, "functions.test.mjs"),
|
||||
path.join(testsDir, "lib-contracts.test.mjs"),
|
||||
path.join(testsDir, "sales-prompts.test.mjs"),
|
||||
];
|
||||
|
||||
const child = spawn(process.execPath, ["--test", ...testFiles], {
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
child.on("close", (code) => {
|
||||
process.exitCode = code ?? 1;
|
||||
});
|
||||
536
tests/sales-prompts.test.mjs
Normal file
536
tests/sales-prompts.test.mjs
Normal file
@@ -0,0 +1,536 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
argValue,
|
||||
executionModes,
|
||||
flagsInArgs,
|
||||
runFetch,
|
||||
testsDir,
|
||||
withoutFlags,
|
||||
} from "./helpers/cli.mjs";
|
||||
import {
|
||||
catalogMentionsIntent,
|
||||
documentedFlags,
|
||||
parseCurrentWorkloadDefaults,
|
||||
parseDefaultWeekYear,
|
||||
parsePrimaryCommercialSubjectArea,
|
||||
parsePrimaryCommercialSourceName,
|
||||
readSkillCatalogs,
|
||||
} from "./helpers/catalogs.mjs";
|
||||
import {
|
||||
buildLiveExampleContext,
|
||||
placeholderKeysForItem,
|
||||
resolvePromptCase,
|
||||
} from "./helpers/examples.mjs";
|
||||
|
||||
const liveEnabled = process.env.KNOWLEDGE_ONE_VIEW_LIVE === "1";
|
||||
const catalogs = await readSkillCatalogs();
|
||||
const promptCases = JSON.parse(
|
||||
await fs.readFile(path.join(testsDir, "prompts", "sales-prompts.json"), "utf8")
|
||||
);
|
||||
|
||||
function resolveArgs(args, examples = {}, { allowUnresolved = true } = {}) {
|
||||
const weekYear = parseDefaultWeekYear(catalogs.scriptCatalog);
|
||||
return args
|
||||
.map((arg) => (arg === "catalog-default" ? String(weekYear) : arg))
|
||||
.map((arg) => String(arg).replace(/\{\{([a-zA-Z0-9]+)\}\}/g, (match, key) => {
|
||||
if (examples[key]) return examples[key];
|
||||
if (allowUnresolved) return match;
|
||||
throw new Error(`Missing live example value for {{${key}}}.`);
|
||||
}));
|
||||
}
|
||||
|
||||
function resolvedCase(item, examples = {}, { allowUnresolved = true } = {}) {
|
||||
return resolvePromptCase(item, examples, { allowUnresolved });
|
||||
}
|
||||
|
||||
function caseArgs(item, examples = {}, options = {}) {
|
||||
return resolveArgs(resolvedCase(item, examples, options).args, examples, options);
|
||||
}
|
||||
|
||||
function liveArgs(item, examples = {}, options = {}) {
|
||||
const resolved = resolvedCase(item, examples, options);
|
||||
return resolveArgs(resolved.liveArgs || withoutFlags(resolved.args, ["--render-sql"]), examples, options);
|
||||
}
|
||||
|
||||
function promptNeedsInitialContext(prompt) {
|
||||
return /\b(manager|abaixo|hierarquia|owners abaixo)\b/i.test(prompt);
|
||||
}
|
||||
|
||||
function promptNeedsWeekReference(prompt) {
|
||||
return /\bweek\b|\bwk\b|\bw\d+\b/i.test(prompt);
|
||||
}
|
||||
|
||||
function globalParameterFlags(parameters) {
|
||||
return new Set((parameters || []).map((parameter) => parameter.cli));
|
||||
}
|
||||
|
||||
test("sales prompt commands are short, direct, and documented in references", () => {
|
||||
const flagsFromReferences = documentedFlags(catalogs);
|
||||
|
||||
for (const item of promptCases) {
|
||||
const args = caseArgs(item);
|
||||
const modes = executionModes(args);
|
||||
|
||||
assert.deepEqual(modes, [args.includes("--intent") ? "--intent" : "--query"], item.id);
|
||||
assert.equal(args.includes("--catalog"), false, `${item.id} should not start with discovery.`);
|
||||
assert.equal(args.includes("--check-auth"), false, `${item.id} should not add auth checks to the normal fetch.`);
|
||||
assert.ok(flagsInArgs(args).length <= 6, `${item.id} should remain easy for an agent to execute.`);
|
||||
|
||||
for (const flag of flagsInArgs(args)) {
|
||||
assert.ok(flagsFromReferences.has(flag), `${item.id} uses undocumented flag ${flag}.`);
|
||||
}
|
||||
|
||||
assert.ok(item.catalogRefs.includes("SKILL.md"), `${item.id} must start from SKILL.md.`);
|
||||
if (promptNeedsInitialContext(item.prompt)) {
|
||||
assert.ok(
|
||||
item.catalogRefs.includes("references/initial-context-catalog.md"),
|
||||
`${item.id} should point manager or hierarchy wording to initial-context-catalog.`
|
||||
);
|
||||
}
|
||||
if (promptNeedsWeekReference(item.prompt)) {
|
||||
assert.ok(
|
||||
item.catalogRefs.includes("references/script-catalog.md#week-reference"),
|
||||
`${item.id} should point week wording to the reference week table.`
|
||||
);
|
||||
}
|
||||
|
||||
const intent = argValue(args, "--intent");
|
||||
if (intent) {
|
||||
assert.equal(catalogMentionsIntent(catalogs, intent), true, `${item.id} intent is not mentioned in references.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("sales prompt people examples are resolved dynamically, not hard-coded", () => {
|
||||
const serializedPrompts = JSON.stringify(promptCases);
|
||||
assert.doesNotMatch(serializedPrompts, /resource\.manager(?:\.a|\.b)?@oracle\.com/);
|
||||
assert.doesNotMatch(serializedPrompts, /territory\.(?:owner|manager)@oracle\.com/);
|
||||
assert.doesNotMatch(serializedPrompts, /resource\.member\b/);
|
||||
|
||||
const requiredPlaceholders = new Set([
|
||||
"resourceManagerEmail",
|
||||
"resourceManagerEmailA",
|
||||
"resourceManagerEmailB",
|
||||
"resourceManagerEmailList",
|
||||
"territoryOwnerEmail",
|
||||
"territoryManagerEmail",
|
||||
"resourceMember",
|
||||
]);
|
||||
const usedPlaceholders = new Set(promptCases.flatMap((item) => placeholderKeysForItem(item)));
|
||||
for (const placeholder of requiredPlaceholders) {
|
||||
assert.ok(usedPlaceholders.has(placeholder), `Expected {{${placeholder}}} in prompt test definitions.`);
|
||||
}
|
||||
});
|
||||
|
||||
test("intent prompt commands match the live CLI contract without local flag copies", async () => {
|
||||
const parameterCatalog = await runFetch(["--list-parameters"]);
|
||||
const globalFlags = globalParameterFlags(parameterCatalog.json.parameters);
|
||||
|
||||
for (const item of promptCases) {
|
||||
const args = caseArgs(item);
|
||||
const intent = argValue(args, "--intent");
|
||||
if (!intent) continue;
|
||||
|
||||
const result = await runFetch(["--describe-intent", intent]);
|
||||
const intentFlags = new Set((result.json.intent.parameters || []).map((parameter) => parameter.cli));
|
||||
|
||||
assert.equal(result.json.intent.name, intent, item.id);
|
||||
assert.equal(catalogMentionsIntent(catalogs, intent), true, item.id);
|
||||
|
||||
for (const flag of flagsInArgs(args)) {
|
||||
if (flag === "--intent") continue;
|
||||
assert.ok(globalFlags.has(flag), `${item.id} uses a flag missing from --list-parameters: ${flag}`);
|
||||
assert.ok(
|
||||
intentFlags.has(flag) || ["--limit"].includes(flag),
|
||||
`${item.id} uses a flag that is not part of the intent contract: ${flag}`
|
||||
);
|
||||
}
|
||||
|
||||
if (["workloads-by-owner-current", "workloads-by-owner-manager-current"].includes(intent)) {
|
||||
const expectedDefaults = parseCurrentWorkloadDefaults(catalogs, intent);
|
||||
assert.match(result.json.intent.description, /current/i);
|
||||
assert.ok(
|
||||
(result.json.intent.parameters || []).some(
|
||||
(parameter) => parameter.cli === "--quarter" && parameter.default === expectedDefaults.quarter
|
||||
),
|
||||
`${item.id} current workload intent must keep the documented quarter default in the catalog.`
|
||||
);
|
||||
assert.ok(catalogs.allText.includes(expectedDefaults.solution));
|
||||
assert.ok(catalogs.allText.includes(expectedDefaults.revenueTypeGroup));
|
||||
}
|
||||
|
||||
if (intent === "workloads-by-manager-resource-close-window") {
|
||||
assert.match(result.json.intent.description, /Presales Involvement Aux - V1/);
|
||||
assert.match(result.json.intent.description, /does not fetch SR details from DV - SE Team/);
|
||||
assert.doesNotMatch(result.json.intent.description, /resolved SRs/i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("ad hoc sales prompt renders an org-wide opportunity query", async () => {
|
||||
const item = promptCases.find((entry) => entry.id === "ad-hoc-query");
|
||||
const args = caseArgs(item);
|
||||
const result = await runFetch(args);
|
||||
const selectAliases = argValue(args, "--select").split(",").map((value) => value.trim());
|
||||
|
||||
assert.equal(result.json.requestMode, "query");
|
||||
assert.equal(result.json.scope.scopeMode, "org-wide");
|
||||
assert.ok(catalogs.allText.includes(result.json.query.source), "Query source should be documented.");
|
||||
assert.match(result.json.expandedSql, new RegExp(escapeRegExp(parsePrimaryCommercialSubjectArea(catalogs.apiMap))));
|
||||
assert.doesNotMatch(result.json.expandedSql, /SEC_ARIA_SI-SR-TEAM/);
|
||||
|
||||
for (const alias of selectAliases) {
|
||||
assert.ok(catalogs.allText.includes(alias), `Query alias ${alias} should be documented in references.`);
|
||||
assert.ok(result.json.columns.includes(alias), `Rendered query should keep selected alias ${alias}.`);
|
||||
}
|
||||
});
|
||||
|
||||
test("org-wide query money aliases use the pipeline measure that Oracle DV accepts", async () => {
|
||||
const result = await runFetch([
|
||||
"--query",
|
||||
"--select",
|
||||
"opportunityId,customerName,closeDate,bookingValue,workloadAmount",
|
||||
"--where",
|
||||
"closeDate >= DATE '2026-04-26' AND closeDate <= DATE '2026-05-02' AND opportunityStatus IN ('Open','Won','Won Pending') AND cluster = 'Brazil'",
|
||||
"--render-sql",
|
||||
]);
|
||||
|
||||
assert.equal(result.json.requestMode, "query");
|
||||
assert.equal(result.json.scope.scopeMode, "org-wide");
|
||||
assert.match(result.json.expandedSql, /"Columns"\."Pipeline" AS bookingValue/);
|
||||
assert.match(result.json.expandedSql, /"Columns"\."Pipeline" AS workloadAmount/);
|
||||
assert.doesNotMatch(result.json.expandedSql, /"Columns"\."Opportunity Value" AS bookingValue/);
|
||||
assert.doesNotMatch(result.json.expandedSql, /"Columns"\."Workload Amount" AS workloadAmount/);
|
||||
assert.match(result.json.expandedSql, /"Columns"\."Cluster" = 'Brazil'/);
|
||||
assert.match(result.json.expandedSql, /"Columns"\."Revenue Line Close Date" >= DATE '2026-04-26'/);
|
||||
});
|
||||
|
||||
test("CLI rejects invalid limit values before backend execution", async () => {
|
||||
for (const value of ["abc", "0", "-1", "1.5"]) {
|
||||
const result = await runFetch(
|
||||
[
|
||||
"--render-sql",
|
||||
"--query",
|
||||
"--select",
|
||||
"opportunityId",
|
||||
"--limit",
|
||||
value,
|
||||
],
|
||||
{ expectCode: 1 }
|
||||
);
|
||||
assert.match(result.stderr, new RegExp(`Unsupported --limit "${value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`));
|
||||
assert.equal(result.stdout, "");
|
||||
}
|
||||
});
|
||||
|
||||
test(
|
||||
"sales prompt commands run against Oracle DV when live mode is enabled",
|
||||
{ skip: liveEnabled ? false : "set KNOWLEDGE_ONE_VIEW_LIVE=1 to run backend checks" },
|
||||
async (t) => {
|
||||
assert.ok(
|
||||
process.env.ORACLE_SI_RUNTIME_HOME || process.env.ORACLE_SI_TEST_RUNTIME_HOME,
|
||||
"Set ORACLE_SI_RUNTIME_HOME or ORACLE_SI_TEST_RUNTIME_HOME for live DV tests."
|
||||
);
|
||||
|
||||
const sessionContext = await runFetch(
|
||||
["--view", "data-quality-session-context", "--limit", "1"],
|
||||
{ timeoutMs: 120000 }
|
||||
);
|
||||
assert.ok(Array.isArray(sessionContext.json.records), sessionContext.stdout || sessionContext.stderr);
|
||||
assert.ok(sessionContext.json.apiMetrics.totalRowsPulledFromApi >= 0);
|
||||
assert.ok(sessionContext.args.includes("--no-cache"), "The live warm-up must run with --no-cache.");
|
||||
|
||||
const liveExampleContext = await buildLiveExampleContext(catalogs);
|
||||
const failures = [];
|
||||
const tableRows = [];
|
||||
|
||||
for (const item of promptCases.filter((entry) => entry.live !== false)) {
|
||||
const resolvedItem = {
|
||||
...resolvedCase(item, liveExampleContext.examples, { allowUnresolved: false }),
|
||||
placeholderKeys: placeholderKeysForItem(item),
|
||||
};
|
||||
let result = null;
|
||||
let checks = [];
|
||||
let commandError = null;
|
||||
try {
|
||||
result = await runFetch(liveArgs(item, liveExampleContext.examples, { allowUnresolved: false }), { timeoutMs: 180000 });
|
||||
checks = evaluateLivePrompt(resolvedItem, result);
|
||||
} catch (error) {
|
||||
commandError = error;
|
||||
checks = [
|
||||
{
|
||||
name: "command exited successfully",
|
||||
pass: false,
|
||||
actual: error.message,
|
||||
expected: "JSON payload with exit code 0",
|
||||
},
|
||||
];
|
||||
}
|
||||
tableRows.push(buildPromptTableRow(resolvedItem, result, checks, commandError, liveExampleContext));
|
||||
|
||||
for (const check of checks) {
|
||||
if (!check.pass) {
|
||||
failures.push(`${item.id} :: ${check.name} :: expected ${formatValue(check.expected)}, got ${formatValue(check.actual)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.diagnostic(formatPromptResultsTable(tableRows));
|
||||
assert.deepEqual(failures, [], failures.join("\n"));
|
||||
}
|
||||
);
|
||||
|
||||
function evaluateLivePrompt(item, result) {
|
||||
const payload = result.json;
|
||||
const expected = item.liveExpect || {};
|
||||
const checks = [];
|
||||
|
||||
checks.push({
|
||||
name: "command uses --no-cache",
|
||||
pass: result.args.includes("--no-cache"),
|
||||
actual: result.args.includes("--no-cache"),
|
||||
expected: true,
|
||||
});
|
||||
checks.push({
|
||||
name: "records array exists",
|
||||
pass: Array.isArray(payload.records),
|
||||
actual: Array.isArray(payload.records),
|
||||
expected: true,
|
||||
});
|
||||
checks.push({
|
||||
name: "api rows metric exists",
|
||||
pass: typeof payload.apiMetrics?.totalRowsPulledFromApi === "number",
|
||||
actual: typeof payload.apiMetrics?.totalRowsPulledFromApi,
|
||||
expected: "number",
|
||||
});
|
||||
|
||||
const intent = argValue(caseArgs(item), "--intent");
|
||||
checks.push(
|
||||
intent
|
||||
? {
|
||||
name: "intent echoed by CLI",
|
||||
pass: payload.request?.intent === intent,
|
||||
actual: payload.request?.intent,
|
||||
expected: intent,
|
||||
}
|
||||
: {
|
||||
name: "query mode echoed by CLI",
|
||||
pass: payload.request?.mode === "query",
|
||||
actual: payload.request?.mode,
|
||||
expected: "query",
|
||||
}
|
||||
);
|
||||
|
||||
if (expected.scopeMode) {
|
||||
checks.push({
|
||||
name: "scope mode",
|
||||
pass: payload.scope?.scopeMode === expected.scopeMode,
|
||||
actual: payload.scope?.scopeMode,
|
||||
expected: expected.scopeMode,
|
||||
});
|
||||
}
|
||||
|
||||
if (expected.dataSource === "primaryCommercial") {
|
||||
const expectedSource = parsePrimaryCommercialSourceName(catalogs.apiMap);
|
||||
checks.push({
|
||||
name: "primary commercial data source from references/api-map.md",
|
||||
pass: payload.request?.dataSource === expectedSource,
|
||||
actual: payload.request?.dataSource,
|
||||
expected: expectedSource,
|
||||
});
|
||||
}
|
||||
|
||||
if (expected.allowFallback === false) {
|
||||
checks.push({
|
||||
name: "no fallback for org-wide commercial prompt",
|
||||
pass: fallbackUsed(payload) === false,
|
||||
actual: fallbackUsed(payload),
|
||||
expected: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (expected.noDvSeTeamQueries === true) {
|
||||
const dvSeTeamQueries = (payload.apiMetrics?.queries || [])
|
||||
.filter((query) => query?.source === "DV - SE Team")
|
||||
.map((query) => query.label);
|
||||
checks.push({
|
||||
name: "no DV - SE Team execution for involvement prompt",
|
||||
pass: dvSeTeamQueries.length === 0,
|
||||
actual: dvSeTeamQueries,
|
||||
expected: [],
|
||||
});
|
||||
}
|
||||
|
||||
if (Number.isFinite(expected.minRecords)) {
|
||||
checks.push({
|
||||
name: "minimum final records",
|
||||
pass: Array.isArray(payload.records) && payload.records.length >= expected.minRecords,
|
||||
actual: Array.isArray(payload.records) ? payload.records.length : null,
|
||||
expected: `>= ${expected.minRecords}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (Number.isFinite(expected.minApiRows)) {
|
||||
checks.push({
|
||||
name: "minimum raw API rows",
|
||||
pass: payload.apiMetrics?.totalRowsPulledFromApi >= expected.minApiRows,
|
||||
actual: payload.apiMetrics?.totalRowsPulledFromApi,
|
||||
expected: `>= ${expected.minApiRows}`,
|
||||
});
|
||||
}
|
||||
|
||||
return checks;
|
||||
}
|
||||
|
||||
function buildPromptTableRow(item, result, checks, commandError = null, liveExampleContext = null) {
|
||||
const passed = checks.every((check) => check.pass);
|
||||
return {
|
||||
id: item.id,
|
||||
prompt: item.prompt,
|
||||
resultado: buildResultCell(result, checks, passed),
|
||||
trace: buildTraceCell(result, commandError, liveExampleContext, item),
|
||||
escopo: buildScopeCell(result),
|
||||
};
|
||||
}
|
||||
|
||||
function buildResultCell(result, checks, passed) {
|
||||
if (!result) {
|
||||
return `FAIL; ${checks.map((check) => `${check.name}: ${formatValue(check.actual)}`).join("; ")}`;
|
||||
}
|
||||
const records = Array.isArray(result.json?.records) ? result.json.records.length : "n/a";
|
||||
const apiRows = result.json?.apiMetrics?.totalRowsPulledFromApi ?? "n/a";
|
||||
const failedChecks = checks.filter((check) => !check.pass).map((check) => check.name);
|
||||
return [
|
||||
passed ? "PASS" : "FAIL",
|
||||
`records=${records}`,
|
||||
`apiRows=${apiRows}`,
|
||||
failedChecks.length > 0 ? `falhas=${failedChecks.join(", ")}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("; ");
|
||||
}
|
||||
|
||||
function buildTraceCell(result, commandError = null, liveExampleContext = null, item = null) {
|
||||
const resolverTrace = buildResolverTrace(liveExampleContext, item);
|
||||
if (!result) {
|
||||
return [
|
||||
resolverTrace,
|
||||
`fetch-sales-intelligence.mjs ${formatArgs(commandError?.args || [])} -> erro: ${commandError?.message || "unknown error"}`,
|
||||
].filter(Boolean).join("; ");
|
||||
}
|
||||
const payload = result.json;
|
||||
const commandTrace = `fetch-sales-intelligence.mjs ${formatArgs(result.args)} -> ok code=${result.code} durationMs=${Math.round(result.durationMs)}`;
|
||||
const source = payload?.request?.dataSource || payload?.scope?.baseSource || "unknown source";
|
||||
if (fallbackUsed(payload)) {
|
||||
const primaryError = payload?.request?.primarySqlError || firstFallbackWarning(payload) || "primary source failed";
|
||||
return [resolverTrace, `${commandTrace}; primary -> erro: ${primaryError}; fallback ${source} -> ok`]
|
||||
.filter(Boolean)
|
||||
.join("; ");
|
||||
}
|
||||
return [resolverTrace, `${commandTrace}; primary ${source} -> ok`].filter(Boolean).join("; ");
|
||||
}
|
||||
|
||||
function buildScopeCell(result) {
|
||||
if (!result?.json) return "sem payload; escopo desconhecido";
|
||||
const payload = result.json;
|
||||
const scopeMode = payload.scope?.scopeMode || payload.request?.scopeMode || "unknown";
|
||||
const source = payload.request?.dataSource || payload.scope?.baseSource || "unknown source";
|
||||
if (fallbackUsed(payload)) {
|
||||
return `${scopeMode}; caiu para fallback ${source}; ${scopeDisclosure(payload)}`;
|
||||
}
|
||||
return `${scopeMode}; sem fallback; fonte ${source}; ${scopeDisclosure(payload)}`;
|
||||
}
|
||||
|
||||
function formatPromptResultsTable(rows) {
|
||||
const header = ["id", "prompt", "resultado", "trace da execucao do agente", "descricao escopo/fallback"];
|
||||
const lines = [
|
||||
`| ${header.join(" | ")} |`,
|
||||
`| ${header.map(() => "---").join(" | ")} |`,
|
||||
...rows.map((row) =>
|
||||
`| ${markdownCell(row.id)} | ${markdownCell(row.prompt)} | ${markdownCell(row.resultado)} | ${markdownCell(row.trace)} | ${markdownCell(row.escopo)} |`
|
||||
),
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function formatArgs(args) {
|
||||
return (args || [])
|
||||
.map((arg) => (arg === "--runtime-home" ? "--runtime-home <test-runtime>" : arg))
|
||||
.filter((arg, index, list) => list[index - 1] !== "--runtime-home <test-runtime>")
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function scopeDisclosure(payload) {
|
||||
return payload?.scope?.disclosureLines?.join(" ") || payload?.scope?.explanation || "";
|
||||
}
|
||||
|
||||
function buildResolverTrace(liveExampleContext, item) {
|
||||
const keys = new Set(item?.placeholderKeys || placeholderKeysForItem(item || {}));
|
||||
if (!keys.size || !liveExampleContext) return "";
|
||||
const traces = [];
|
||||
for (const trace of liveExampleContext.trace || []) {
|
||||
if (!trace.profiles?.some((profile) => keys.has(profile))) continue;
|
||||
const relevantProfiles = trace.profiles
|
||||
.filter((profile) => keys.has(profile))
|
||||
.map((profile) => `${profile}=${liveExampleContext.examples[profile]}`)
|
||||
.join(", ");
|
||||
traces.push(`${trace.script} ${formatArgs(trace.args)} -> ${trace.result}; selected ${relevantProfiles}`);
|
||||
}
|
||||
return traces.join("; ");
|
||||
}
|
||||
|
||||
function firstFallbackWarning(payload) {
|
||||
return (payload?.warnings || []).find((warning) => /fell back|fallback/i.test(warning));
|
||||
}
|
||||
|
||||
function markdownCell(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/\r?\n/g, "<br>")
|
||||
.replace(/\|/g, "\\|");
|
||||
}
|
||||
|
||||
function buildPromptDiagnostic(item, result, checks) {
|
||||
return {
|
||||
test: item.id,
|
||||
prompt: item.prompt,
|
||||
input: {
|
||||
args: result ? result.args : liveArgs(item),
|
||||
},
|
||||
output: result
|
||||
? {
|
||||
records: Array.isArray(result.json?.records) ? result.json.records.length : null,
|
||||
apiRows: result.json?.apiMetrics?.totalRowsPulledFromApi ?? null,
|
||||
scopeMode: result.json?.scope?.scopeMode ?? null,
|
||||
dataSource: result.json?.request?.dataSource ?? null,
|
||||
fallbackUsed: fallbackUsed(result.json),
|
||||
warnings: result.json?.warnings || [],
|
||||
}
|
||||
: null,
|
||||
evaluation: checks.map((check) => ({
|
||||
name: check.name,
|
||||
passed: check.pass,
|
||||
expected: check.expected,
|
||||
actual: check.actual,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
function fallbackUsed(payload) {
|
||||
const warningText = (payload?.warnings || []).join("\n");
|
||||
return Boolean(
|
||||
payload?.request?.fallbackUsed ||
|
||||
payload?.scope?.fallbackUsed ||
|
||||
/fell back|fallback used/i.test(warningText)
|
||||
);
|
||||
}
|
||||
|
||||
function formatValue(value) {
|
||||
return typeof value === "string" ? value : JSON.stringify(value);
|
||||
}
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
Reference in New Issue
Block a user