@@ -11,6 +11,7 @@ import { Common } from '../../../src/modules/common/Common.js';
1111import LoggingContext from '../../../src/modules/logging/LoggingContext.js' ;
1212import LoggingService from '../../../src/modules/logging/LoggingService.js' ;
1313import { ADDON_EVENTS , DATA_MEDIA_TYPE , OPERATION } from '../../../src/modules/common/Enumerations.js' ;
14+ import { TARGET_FULL_QUERY_RECORDS_THRESHOLD } from '../../../src/modules/constants/Constants.js' ;
1415import { UnresolvableWarning } from '../../../src/modules/models/common/UnresolvableWarning.js' ;
1516import MigrationJob from '../../../src/modules/models/job/MigrationJob.js' ;
1617import type { IMetadataProvider } from '../../../src/modules/models/job/IMetadataProvider.js' ;
@@ -22,6 +23,7 @@ import ScriptOrg from '../../../src/modules/models/script/ScriptOrg.js';
2223import SFieldDescribe from '../../../src/modules/models/sf/SFieldDescribe.js' ;
2324import SObjectDescribe from '../../../src/modules/models/sf/SObjectDescribe.js' ;
2425import OrgConnectionAdapter from '../../../src/modules/org/OrgConnectionAdapter.js' ;
26+ import OrgDataService from '../../../src/modules/org/OrgDataService.js' ;
2527
2628type FieldInitType = Partial < SFieldDescribe > & { name : string } ;
2729type OrgConnectionStubType = {
@@ -99,6 +101,162 @@ describe('MigrationJob', () => {
99101 Common . logger = originalLogger ;
100102 } ) ;
101103
104+ const prepareSingleObjectStrategyJobAsync = async (
105+ object : ScriptObject ,
106+ describe : SObjectDescribe ,
107+ targetCount : number
108+ ) : Promise < MigrationJob > => {
109+ const script = new Script ( ) ;
110+ script . objectSets = [ new ScriptObjectSet ( [ object ] ) ] ;
111+
112+ const sourceOrg = new ScriptOrg ( ) ;
113+ sourceOrg . name = 'source' ;
114+ sourceOrg . media = DATA_MEDIA_TYPE . Org ;
115+ sourceOrg . script = script ;
116+ const targetOrg = new ScriptOrg ( ) ;
117+ targetOrg . name = 'target' ;
118+ targetOrg . media = DATA_MEDIA_TYPE . Org ;
119+ targetOrg . script = script ;
120+ script . sourceOrg = sourceOrg ;
121+ script . targetOrg = targetOrg ;
122+
123+ const metadataProvider = createMetadataProvider ( new Map ( [ [ object . name , describe ] ] ) ) ;
124+ const job = new MigrationJob ( { script, metadataProvider } ) ;
125+ const originalConnection = OrgConnectionAdapter . getConnectionForAliasAsync . bind ( OrgConnectionAdapter ) ;
126+ const queryOrgAsyncDescriptor = Object . getOwnPropertyDescriptor ( OrgDataService . prototype , 'queryOrgAsync' ) ;
127+ if ( typeof queryOrgAsyncDescriptor ?. value !== 'function' ) {
128+ throw new Error ( 'OrgDataService.queryOrgAsync descriptor was not found.' ) ;
129+ }
130+ const originalQueryOrgAsync = queryOrgAsyncDescriptor . value as OrgDataService [ 'queryOrgAsync' ] ;
131+ OrgConnectionAdapter . getConnectionForAliasAsync = async ( ) => createOrgConnectionStub ( ) as never ;
132+ OrgDataService . prototype . queryOrgAsync = async (
133+ query : string ,
134+ org : ScriptOrg
135+ ) : Promise < Array < Record < string , unknown > > > => {
136+ if ( ! query . includes ( 'COUNT' ) ) {
137+ return [ ] ;
138+ }
139+ return [ { CNT : org . name === 'target' ? targetCount : 1 } ] ;
140+ } ;
141+
142+ try {
143+ await job . loadAsync ( ) ;
144+ await job . setupAsync ( ) ;
145+ await job . prepareAsync ( ) ;
146+ } finally {
147+ OrgConnectionAdapter . getConnectionForAliasAsync = originalConnection ;
148+ OrgDataService . prototype . queryOrgAsync = originalQueryOrgAsync ;
149+ }
150+
151+ return job ;
152+ } ;
153+
154+ it ( 'uses filtered target queries for master objects with simple external id above the target threshold' , async ( ) => {
155+ const account = new ScriptObject ( 'Account' ) ;
156+ account . operation = OPERATION . Upsert ;
157+ account . query = 'SELECT Id, External_Key__c FROM Account' ;
158+ account . externalId = 'External_Key__c' ;
159+
160+ const job = await prepareSingleObjectStrategyJobAsync (
161+ account ,
162+ createDescribe ( 'Account' , [ { name : 'Id' } , { name : 'External_Key__c' , updateable : true , creatable : true } ] ) ,
163+ TARGET_FULL_QUERY_RECORDS_THRESHOLD + 1
164+ ) ;
165+
166+ const task = job . getTaskBySObjectName ( 'Account' ) ;
167+ assert . equal ( task ?. scriptObject . processAllSource , true ) ;
168+ assert . equal ( task ?. scriptObject . processAllTarget , false ) ;
169+ } ) ;
170+
171+ it ( 'uses full target query for simple external id below the target threshold' , async ( ) => {
172+ const account = new ScriptObject ( 'Account' ) ;
173+ account . operation = OPERATION . Upsert ;
174+ account . query = 'SELECT Id, External_Key__c FROM Account' ;
175+ account . externalId = 'External_Key__c' ;
176+
177+ const job = await prepareSingleObjectStrategyJobAsync (
178+ account ,
179+ createDescribe ( 'Account' , [ { name : 'Id' } , { name : 'External_Key__c' , updateable : true , creatable : true } ] ) ,
180+ TARGET_FULL_QUERY_RECORDS_THRESHOLD - 1
181+ ) ;
182+
183+ const task = job . getTaskBySObjectName ( 'Account' ) ;
184+ assert . equal ( task ?. scriptObject . processAllTarget , true ) ;
185+ } ) ;
186+
187+ it ( 'uses the object target full-query threshold override' , async ( ) => {
188+ const account = new ScriptObject ( 'Account' ) ;
189+ account . operation = OPERATION . Upsert ;
190+ account . query = 'SELECT Id, External_Key__c FROM Account' ;
191+ account . externalId = 'External_Key__c' ;
192+ account . targetFullQueryRecordsThreshold = TARGET_FULL_QUERY_RECORDS_THRESHOLD + 10 ;
193+
194+ const job = await prepareSingleObjectStrategyJobAsync (
195+ account ,
196+ createDescribe ( 'Account' , [ { name : 'Id' } , { name : 'External_Key__c' , updateable : true , creatable : true } ] ) ,
197+ TARGET_FULL_QUERY_RECORDS_THRESHOLD + 1
198+ ) ;
199+
200+ const task = job . getTaskBySObjectName ( 'Account' ) ;
201+ assert . equal ( task ?. scriptObject . processAllTarget , true ) ;
202+ } ) ;
203+
204+ it ( 'keeps queryAllTarget on full target query regardless of target count' , async ( ) => {
205+ const account = new ScriptObject ( 'Account' ) ;
206+ account . operation = OPERATION . Upsert ;
207+ account . query = 'SELECT Id, External_Key__c FROM Account' ;
208+ account . externalId = 'External_Key__c' ;
209+ account . queryAllTarget = true ;
210+
211+ const job = await prepareSingleObjectStrategyJobAsync (
212+ account ,
213+ createDescribe ( 'Account' , [ { name : 'Id' } , { name : 'External_Key__c' , updateable : true , creatable : true } ] ) ,
214+ TARGET_FULL_QUERY_RECORDS_THRESHOLD + 1
215+ ) ;
216+
217+ const task = job . getTaskBySObjectName ( 'Account' ) ;
218+ assert . equal ( task ?. scriptObject . processAllTarget , true ) ;
219+ } ) ;
220+
221+ it ( 'uses full target query for complex external id regardless of target count' , async ( ) => {
222+ const account = new ScriptObject ( 'Account' ) ;
223+ account . operation = OPERATION . Upsert ;
224+ account . query = 'SELECT Id, External_Key__c, Name FROM Account' ;
225+ account . externalId = 'External_Key__c;Name' ;
226+
227+ const job = await prepareSingleObjectStrategyJobAsync (
228+ account ,
229+ createDescribe ( 'Account' , [
230+ { name : 'Id' } ,
231+ { name : 'External_Key__c' , updateable : true , creatable : true } ,
232+ { name : 'Name' , nameField : true , updateable : true , creatable : true } ,
233+ ] ) ,
234+ TARGET_FULL_QUERY_RECORDS_THRESHOLD + 1
235+ ) ;
236+
237+ const task = job . getTaskBySObjectName ( 'Account' ) ;
238+ assert . equal ( task ?. scriptObject . processAllTarget , true ) ;
239+ } ) ;
240+
241+ it ( 'uses full target query for special objects regardless of target count' , async ( ) => {
242+ const recordType = new ScriptObject ( 'RecordType' ) ;
243+ recordType . query = 'SELECT Id, DeveloperName, NamespacePrefix, SobjectType FROM RecordType' ;
244+
245+ const job = await prepareSingleObjectStrategyJobAsync (
246+ recordType ,
247+ createDescribe ( 'RecordType' , [
248+ { name : 'Id' } ,
249+ { name : 'DeveloperName' } ,
250+ { name : 'NamespacePrefix' } ,
251+ { name : 'SobjectType' } ,
252+ ] ) ,
253+ TARGET_FULL_QUERY_RECORDS_THRESHOLD + 1
254+ ) ;
255+
256+ const task = job . getTaskBySObjectName ( 'RecordType' ) ;
257+ assert . equal ( task ?. scriptObject . processAllTarget , true ) ;
258+ } ) ;
259+
102260 it ( 'orders tasks by RecordType, readonly, and lookup parents' , async ( ) => {
103261 const recordType = new ScriptObject ( 'RecordType' ) ;
104262 recordType . operation = OPERATION . Readonly ;
0 commit comments