Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 1 addition & 82 deletions src/utils/nativeInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ export type NativeInitOptions = {
export type NativeInitResult = {
filesCreated: string[];
configPath: string;
testJsonPath?: string;
};

const ENTRYPOINT_FILE = 'entrypoint.py';
const CONFIG_FILE = 'config.json';
const PAYLOAD_DIR = 'payload';
const TESTS_DIR = 'tests';
const TEST_FILE = 'test.json';
const SDK_CONFIG_DIR = '.datacustomcode_proj';
const SDK_CONFIG_FILE = 'sdk_config.json';

Expand All @@ -62,7 +59,6 @@ const FEATURE_TEMPLATE_DIRS = new Set<string>(Object.values(FEATURE_TEMPLATE_MAP
* 2. Write `<packageDir>/.datacustomcode_proj/sdk_config.json` with the package type.
* 3. Generate the initial `config.json` next to the entrypoint (with sdkVersion for
* scripts), then re-scan the entrypoint to fill in DLO/DMO permissions.
* 4. For function packages, best-effort generate `tests/test.json` for known features.
*/
export async function executeNativeInit(opts: NativeInitOptions): Promise<NativeInitResult> {
const { codeType, packageDir, pythonPackageLocation, pythonPackageVersion, useInFeature } = opts;
Expand Down Expand Up @@ -90,13 +86,7 @@ export async function executeNativeInit(opts: NativeInitOptions): Promise<Native
const finalConfig = await updateConfig(entrypointPath, codeType, configPath);
await writeJson(configPath, finalConfig);

// 4. Function: best-effort test.json
let testJsonPath: string | undefined;
if (codeType === 'function') {
testJsonPath = await generateFunctionTestFile(entrypointPath, useInFeature);
}

return { filesCreated, configPath, testJsonPath };
return { filesCreated, configPath };
}

async function copyTemplateEntries(
Expand Down Expand Up @@ -185,77 +175,6 @@ function buildInitialConfig(codeType: CodeType, entrypointPath: string, sdkVersi
return { entryPoint: path.basename(entrypointPath) };
}

/**
* Function packages: write a sample `tests/test.json` for known feature types.
* Best-effort — failure does not fail init (matches Python try/except behavior).
*/
async function generateFunctionTestFile(
entrypointPath: string,
useInFeature: string | undefined
): Promise<string | undefined> {
const sample = sampleRequestForFeature(useInFeature);
if (!sample) {
debug('no sample test.json available for feature: %s', useInFeature);
return undefined;
}

try {
const testsDir = path.join(path.dirname(entrypointPath), TESTS_DIR);
await fs.mkdir(testsDir, { recursive: true });
const testJsonPath = path.join(testsDir, TEST_FILE);
await writeJson(testJsonPath, sample);
return testJsonPath;
} catch (err) {
debug('failed to write test.json: %o', err);
return undefined;
}
}

function sampleRequestForFeature(useInFeature: string | undefined): Record<string, unknown> | undefined {
// Mirrors Python's `_generate_model_sample_data` output for SearchIndexChunkingV1Request.
// Values come from the Pydantic field examples on `chunking.py`; pydantic coerces the
// example strings ("1.0", "8.75") to floats during model_dump.
//
// The keys below are snake_case + `__c` Salesforce field names that the wire format
// requires verbatim. Disabling camelcase here is the right tradeoff: prettier's
// `quoteProps: as-needed` reformats quoted keys back to identifiers, so we can't keep
// them quoted to satisfy eslint without also tweaking prettier config repo-wide.
if (useInFeature !== 'SearchIndexChunking') return undefined;
return JSON.parse(`{
"input": [
{
"text": "Online Remittance Instructions\\n\\nTransfer proceeds from the sale of your ESOP/RSUs easily.",
"metadata": {
"type": "text",
"page_number": 1,
"transcript_fields": {
"speaker": "Agent",
"start_timestamp": 1.0,
"end_timestamp": 8.75
},
"text_as_html": "<p>Online Remittance Instructions</p>",
"source_dmo_fields": {
"FilePath__c": "quarterly_report.pdf",
"Size__c": 1377454.0,
"ContentType__c": "pdf",
"LastModified__c": "2026-03-25T02:01:24.918000"
},
"prepend": [
{
"dmo_name": "udmo_1__dlm",
"field_name": "ResolvedFilePath__c",
"value": "udlo_1__dll:quarterly_report.pdf"
}
],
"image_base64": "sample_image_base64",
"image_mime_type": "image/png",
"image_type": "diagram"
}
}
]
}`) as Record<string, unknown>;
}

async function pathExists(p: string): Promise<boolean> {
try {
await fs.access(p);
Expand Down
35 changes: 21 additions & 14 deletions test/utils/nativeInit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import { executeNativeInit } from '../../src/utils/nativeInit.js';
// without depending on the installed Python package, each test builds a fake
// package layout in a temp dir and points init at it.

// Sample request shipped in the chunking template; init must copy it verbatim.
const TEMPLATE_TEST_JSON = { input: [{ text: 'sample text from template' }] };

type FakePackageOptions = {
scriptEntrypoint?: string;
scriptConfig?: Record<string, unknown>;
Expand Down Expand Up @@ -83,6 +86,8 @@ async function buildFakePythonPackage(opts: FakePackageOptions = {}): Promise<st
await fs.mkdir(chunkingPayload, { recursive: true });
await fs.writeFile(path.join(chunkingPayload, 'entrypoint.py'), '# chunking entrypoint\n');
await fs.writeFile(path.join(chunkingPayload, 'config.json'), '{}');
await fs.mkdir(path.join(chunkingPayload, 'tests'), { recursive: true });
await fs.writeFile(path.join(chunkingPayload, 'tests', 'test.json'), JSON.stringify(TEMPLATE_TEST_JSON, null, 2));

return root;
}
Expand Down Expand Up @@ -113,7 +118,6 @@ describe('executeNativeInit', () => {

expect(result.filesCreated).to.deep.equal([packageDir]);
expect(result.configPath).to.equal(path.join(packageDir, 'payload', 'config.json'));
expect(result.testJsonPath).to.be.undefined;

const sdkConfig = JSON.parse(
await fs.readFile(path.join(packageDir, '.datacustomcode_proj', 'sdk_config.json'), 'utf8')
Expand Down Expand Up @@ -211,7 +215,6 @@ describe('executeNativeInit', () => {
});

expect(result.filesCreated).to.deep.equal([packageDir]);
expect(result.testJsonPath).to.equal(path.join(packageDir, 'payload', 'tests', 'test.json'));

const sdkConfig = JSON.parse(
await fs.readFile(path.join(packageDir, '.datacustomcode_proj', 'sdk_config.json'), 'utf8')
Expand All @@ -228,13 +231,11 @@ describe('executeNativeInit', () => {
const entrypoint = await fs.readFile(path.join(packageDir, 'payload', 'entrypoint.py'), 'utf8');
expect(entrypoint).to.include('chunking entrypoint');

// tests/test.json should be a structured sample for SearchIndexChunking.
const sample = JSON.parse(await fs.readFile(result.testJsonPath!, 'utf8')) as {
input: Array<{ text: string; metadata?: { type?: string } }>;
};
expect(sample.input).to.be.an('array').with.length(1);
expect(sample.input[0].text).to.be.a('string').with.length.greaterThan(0);
expect(sample.input[0].metadata?.type).to.equal('text');
// tests/test.json is copied verbatim from the template, not regenerated.
const copied = JSON.parse(
await fs.readFile(path.join(packageDir, 'payload', 'tests', 'test.json'), 'utf8')
) as unknown;
expect(copied).to.deep.equal(TEMPLATE_TEST_JSON);
});

it('does not write test.json for an unknown feature', async () => {
Expand All @@ -248,7 +249,7 @@ describe('executeNativeInit', () => {
useInFeature: 'UnknownFeature',
});

expect(result.testJsonPath).to.be.undefined;
expect(result.filesCreated).to.deep.equal([packageDir]);
let testsDirExists = true;
try {
await fs.access(path.join(packageDir, 'payload', 'tests'));
Expand All @@ -269,7 +270,7 @@ describe('executeNativeInit', () => {
// useInFeature intentionally omitted
});

expect(result.testJsonPath).to.be.undefined;
expect(result.filesCreated).to.deep.equal([packageDir]);
// Base function entrypoint copied (no chunking overlay).
const entrypoint = await fs.readFile(path.join(packageDir, 'payload', 'entrypoint.py'), 'utf8');
expect(entrypoint).to.include('def function(request, runtime: Runtime)');
Expand All @@ -294,10 +295,16 @@ describe('executeNativeInit', () => {
useInFeature: 'SearchIndexChunking',
});

// Init should succeed using only the base function template; test.json is still
// sample-driven (not template-driven), so it gets written.
// Init should succeed using only the base function template. No template ships a
// tests/test.json here, and init must not invent one.
expect(result.filesCreated).to.deep.equal([packageDir]);
expect(result.testJsonPath).to.equal(path.join(packageDir, 'payload', 'tests', 'test.json'));
let testJsonExists = true;
try {
await fs.access(path.join(packageDir, 'payload', 'tests', 'test.json'));
} catch {
testJsonExists = false;
}
expect(testJsonExists).to.be.false;
await fs.rm(customPkg, { recursive: true, force: true });
});

Expand Down
Loading