Read Salesforce CRM Data
Accounts, Opportunities, Contacts, Leads, Organization, custom objects, and schema describes through raw REST calls.
A read-focused preview of the ForgeFX Salesforce CRM skill, its TypeScript helper layer, and the ordered live sanity tests that prove Doppler credentials, SOAP authentication, organization lookup, and newest-record reads are working before agents depend on CRM data.
Accounts, Opportunities, Contacts, Leads, Organization, custom objects, and schema describes through raw REST calls.
Create/update helpers remain dry-run-first and require named user approval plus query-back verification after a write.
Live tests and scripts use Doppler with the ForgeApps production config. Secret values are never printed or embedded here.
Finds required Salesforce env var names injected by Doppler.
Performs SOAP login and validates instance URL, session, and API version.
Reads the org record and asserts the name matches ForgeFX.
Reads the most recently created Account by CreatedDate.
Reads the most recently created Opportunity by CreatedDate.
Reads the most recently created Contact by CreatedDate.
Reads the most recently created Lead by CreatedDate.
Reusable SOAP login, query, create, read, update, delete, and describe helper layer.
SOQL SELECT runner for quick probes and legacy Python capability replacement.
Object fields, create/update flags, and parent relationship checks.
Generic create helper that requires explicit confirmation before mutation.
Verified from the repo root on 2026-07-08 HST using the Doppler-backed command below.
doppler run --project forgeapps --config prd -- pnpm exec vitest run .forgebot/skills/salesforce/tests --coverage.enabled=false
Observed live sanity records: Organization ForgeFX Simulations, newest Account Response Group, newest Opportunity WM Heavy Equipment Operator Simulator, newest Contact Luke Hughes, newest Lead Paul Eldridge.
import { describe, expect, it } from "vitest";
import { login, query, type SalesforceAuth } from "../scripts/salesforce-client.ts";
type NamedSalesforceRecord = {
Id: string;
Name: string;
CreatedDate?: string;
};
const LIVE_TEST_TIMEOUT_MS = 60_000;
const REQUIRED_DOPPLER_SECRETS = [
"SALESFORCE_USERNAME",
"SALESFORCE_PASSWORD",
"SALESFORCE_SECURITY_TOKEN",
] as const;
let auth: SalesforceAuth | undefined;
function missingRequiredSecrets(): string[] {
return REQUIRED_DOPPLER_SECRETS.filter((name) => !process.env[name]?.trim());
}
function requireAuth(): SalesforceAuth {
expect(auth, "The Salesforce authentication sanity check must pass before data tests run.").toBeDefined();
return auth as SalesforceAuth;
}
async function expectMostRecentlyCreatedNamedRecord(objectName: "Account" | "Opportunity" | "Contact" | "Lead"): Promise<void> {
const result = await query<NamedSalesforceRecord>(
requireAuth(),
`SELECT Id, Name, CreatedDate FROM ${objectName} ORDER BY CreatedDate DESC LIMIT 1`,
);
expect(result.totalSize, `${objectName} query should return at least one record.`).toBeGreaterThan(0);
expect(result.records[0]?.Id, `${objectName} Id should be present.`).toMatch(/\S/);
expect(result.records[0]?.Name, `${objectName} Name should be present.`).toMatch(/\S/);
expect(result.records[0]?.CreatedDate, `${objectName} CreatedDate should be present.`).toMatch(/\S/);
}
describe("salesforce live sanity checks", () => {
it("finds required Doppler-provided Salesforce credentials", () => {
expect(
missingRequiredSecrets(),
"Run with: doppler run --project forgeapps --config prd -- pnpm exec vitest run .forgebot/skills/salesforce/tests/live-smoke.test.ts --coverage.enabled=false",
).toEqual([]);
});
it(
"authenticates with Salesforce",
async () => {
auth = await login();
expect(auth.instanceUrl).toMatch(/^https:\/\/.+/);
expect(auth.sessionId).toMatch(/\S/);
expect(auth.apiVersion).toMatch(/^\d+\.\d+$/);
},
LIVE_TEST_TIMEOUT_MS,
);
it(
"reads the ForgeFX organization name",
async () => {
const result = await query<NamedSalesforceRecord>(
requireAuth(),
"SELECT Id, Name FROM Organization LIMIT 1",
);
expect(result.totalSize).toBe(1);
expect(result.records[0]?.Name).toMatch(/ForgeFX/i);
},
LIVE_TEST_TIMEOUT_MS,
);
it("reads the most recently created account", async () => {
await expectMostRecentlyCreatedNamedRecord("Account");
}, LIVE_TEST_TIMEOUT_MS);
it("reads the most recently created opportunity", async () => {
await expectMostRecentlyCreatedNamedRecord("Opportunity");
}, LIVE_TEST_TIMEOUT_MS);
it("reads the most recently created contact", async () => {
await expectMostRecentlyCreatedNamedRecord("Contact");
}, LIVE_TEST_TIMEOUT_MS);
it("reads the most recently created lead", async () => {
await expectMostRecentlyCreatedNamedRecord("Lead");
}, LIVE_TEST_TIMEOUT_MS);
});
import { describe, expect, it } from "vitest";
import { mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { checkSkill } from "../scripts/check-skill.ts";
describe("skill scaffold check", () => {
it("salesforce skill scaffold check passes", () => {
const result = checkSkill();
expect(result.ok, result.errors.join("\n")).toBe(true);
expect(result.frontmatter.name).toMatch(/\S/);
expect(result.frontmatter.description).toMatch(/\S/);
});
it("accepts CRLF frontmatter", () => {
const dir = mkdtempSync(join(tmpdir(), "salesforce-skill-check-"));
writeFileSync(
join(dir, "SKILL.md"),
"---\r\nname: test-skill\r\ndescription: CRLF frontmatter\r\n---\r\n\r\n# Body\r\n",
"utf8"
);
const result = checkSkill(dir);
expect(result.ok, result.errors.join("\n")).toBe(true);
expect(result.frontmatter.name).toBe("test-skill");
});
});