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
38 changes: 23 additions & 15 deletions src/commands/data/setup/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,37 +176,45 @@ export default class SetupTransfer extends SfCommand<SetupTransferResult> {
definition: DefinitionFile,
data: { metadata: Record<string, unknown>; objects: ExportEntity[] }
): MergedPayload {
const defEntityMap = new Map<string, DefinitionEntity>();
for (const entity of definition.objects.list) {
defEntityMap.set(entity.objectName, entity);
const dataEntityMap = new Map<string, ExportEntity>();
for (const entity of data.objects ?? []) {
dataEntityMap.set(entity.objectName, entity);
}

const importSequence = definition.importSequence?.list ?? [];
const metadata = data.metadata ?? {};

const mergedEntities: MergedEntity[] = data.objects.map((dataEntity) => {
const defEntity = defEntityMap.get(dataEntity.objectName);
// Drive the merge from the definition so that every object declared in the
// DefinitionFile is included in the import payload, even when the export
// returned no data record for it (records default to an empty array).
const mergedEntities: MergedEntity[] = definition.objects.list.map((defEntity) => {
const dataEntity = dataEntityMap.get(defEntity.objectName);

const header: MergedEntityHeader = {
objectName: dataEntity.objectName,
fields: defEntity?.fields ?? '',
filterCriteria: defEntity?.filterCriteria ?? '',
foreignKeys: defEntity?.foreignKeys?.list ?? [],
objectName: defEntity.objectName,
fields: defEntity.fields ?? '',
filterCriteria: defEntity.filterCriteria ?? '',
foreignKeys: defEntity.foreignKeys?.list ?? [],
};
if (defEntity?.globalKeyField) {
if (defEntity.globalKeyField) {
header.globalKeyField = defEntity.globalKeyField;
}
if (defEntity?.compositeKeys?.list) {
if (defEntity.compositeKeys?.list) {
header.compositeKeys = defEntity.compositeKeys.list;
}

const merged: MergedEntity = {
header,
objectName: dataEntity.objectName,
records: dataEntity.records,
objectName: defEntity.objectName,
records: dataEntity?.records ?? [],
};
if (dataEntity.recordCount != null) {
merged.recordCount = dataEntity.recordCount;
if (dataEntity) {
if (dataEntity.recordCount != null) {
merged.recordCount = dataEntity.recordCount;
}
} else {
// The export returned nothing for this object; record an explicit zero count.
merged.recordCount = 0;
}
return merged;
});
Expand Down
79 changes: 79 additions & 0 deletions test/commands/data/setup/transfer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,85 @@ describe('data setup transfer', () => {
});
});

describe('custom definition mode', () => {
it('includes every definition object in the import payload even without a data record', async () => {
// Definition declares two objects; export only returns data for Account.
const definition = {
dataSetName: 'customDefinition',
version: '1.0.0',
importSequence: { list: ['Account', 'Contact'] },
objects: {
list: [
{
objectName: 'Account',
globalKeyField: 'Name',
fields: 'Id, Name',
filterCriteria: '',
foreignKeys: { list: [] },
},
{
objectName: 'Contact',
globalKeyField: 'Email',
fields: 'Id, Email',
filterCriteria: '',
foreignKeys: { list: [] },
},
],
},
};

const defFile = path.join(os.tmpdir(), `custom-definition-${Date.now()}.json`);
fs.writeFileSync(defFile, JSON.stringify(definition), 'utf8');

let importPayload:
| { objects: Array<{ objectName: string; records: unknown[]; recordCount?: number }> }
| undefined;
$$.fakeConnectionRequest = (request) => {
if (typeof request === 'string' || (request as { url: string }).url.includes('/export')) {
// Export returns data for Account only — Contact has no records.
return Promise.resolve({
isSuccess: true,
metadata: { dataSetName: 'customDefinition', version: '1.0.0' },
objects: [
{
objectName: 'Account',
recordCount: 1,
records: [{ Id: '001xx000000001', Name: 'Test Account 1' }],
},
],
});
}
importPayload = JSON.parse((request as { body: string }).body) as typeof importPayload;
return Promise.resolve(mockImportResponse);
};

try {
await SetupTransfer.run([
'--extended-definition-file',
defFile,
'--source-org',
'source@test.org',
'--target-org',
'target@test.org',
]);
} finally {
fs.rmSync(defFile, { force: true });
}

expect(importPayload).to.exist;
const objectNames = importPayload!.objects.map((o) => o.objectName);
expect(objectNames).to.deep.equal(['Account', 'Contact']);

const contact = importPayload!.objects.find((o) => o.objectName === 'Contact');
expect(contact!.records).to.deep.equal([]);
expect(contact!.recordCount).to.equal(0);

const account = importPayload!.objects.find((o) => o.objectName === 'Account');
expect(account!.records).to.have.lengthOf(1);
expect(account!.recordCount).to.equal(1);
});
});

describe('error handling', () => {
it('throws error when export API returns error', async () => {
$$.fakeConnectionRequest = (request) => {
Expand Down
Loading