1+ /*
2+ * Copyright (c) 2023, salesforce.com, inc.
3+ * All rights reserved.
4+ * Licensed under the BSD 3-Clause license.
5+ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+ */
7+ import { expect } from 'chai' ;
8+ import { createSandbox } from 'sinon' ;
9+ import { Logger } from '@salesforce/core' ;
10+ import { SourceComponent } from '../../src/resolve/sourceComponent' ;
11+ import { ComponentSet } from '../../src/collections/componentSet' ;
12+ import { RegistryAccess } from '../../src/registry/registryAccess' ;
13+ import { ZipTreeContainer } from '../../src/resolve/treeContainers' ;
14+ import { MetadataConverter } from '../../src/convert/metadataConverter' ;
15+ import { extract } from '../../src/client/retrieveExtract' ;
16+ import { MetadataApiRetrieveOptions } from '../../src/client/types' ;
17+
18+
19+ describe ( 'retrieveExtract Integration' , ( ) => {
20+ const sandbox = createSandbox ( ) ;
21+ let logger : Logger ;
22+ let registry : RegistryAccess ;
23+
24+ beforeEach ( ( ) => {
25+ logger = Logger . childFromRoot ( 'test' ) ;
26+ registry = new RegistryAccess ( ) ;
27+ sandbox . stub ( logger , 'debug' ) ;
28+ } ) ;
29+
30+ afterEach ( ( ) => {
31+ sandbox . restore ( ) ;
32+ } ) ;
33+
34+ describe ( 'ForceIgnore behavior during retrieve' , ( ) => {
35+ it ( 'should disable ForceIgnore during initial processing (useFsForceIgnore: false)' , async ( ) => {
36+ // Mock zip buffer and tree
37+ const mockZipBuffer = Buffer . from ( 'mock zip content' ) ;
38+ const mockTree = { } as ZipTreeContainer ;
39+
40+ // Mock ComponentSet.fromSource to verify useFsForceIgnore is false
41+ const mockComponentSet = new ComponentSet ( [ ] , registry ) ;
42+ sandbox . stub ( mockComponentSet , 'getSourceComponents' ) . returns ( { toArray : ( ) => [ ] } as never ) ;
43+ const fromSourceStub = sandbox . stub ( ComponentSet , 'fromSource' ) . returns ( mockComponentSet ) ;
44+
45+ // Mock ZipTreeContainer.create
46+ sandbox . stub ( ZipTreeContainer , 'create' ) . resolves ( mockTree ) ;
47+
48+ // Mock MetadataConverter
49+ sandbox . stub ( MetadataConverter . prototype , 'convert' ) . resolves ( {
50+ converted : [ ] ,
51+ deleted : [ ]
52+ } ) ;
53+
54+ const options : MetadataApiRetrieveOptions = {
55+ output : '/test/output' ,
56+ registry,
57+ merge : false ,
58+ usernameOrConnection : 'test@example.com'
59+ } ;
60+
61+ await extract ( {
62+ zip : mockZipBuffer ,
63+ options,
64+ logger,
65+ } ) ;
66+
67+ // Verify that ComponentSet.fromSource was called with useFsForceIgnore: false
68+ expect ( fromSourceStub . calledOnce ) . to . be . true ;
69+ const fromSourceArgs = fromSourceStub . firstCall . args [ 0 ] ;
70+ expect ( fromSourceArgs ) . to . have . property ( 'useFsForceIgnore' , false ) ;
71+ } ) ;
72+
73+ it ( 'should apply ForceIgnore filtering after conversion to source format' , async ( ) => {
74+ const mockZipBuffer = Buffer . from ( 'mock zip content' ) ;
75+ const mockTree = { } as ZipTreeContainer ;
76+
77+ sandbox . stub ( ZipTreeContainer , 'create' ) . resolves ( mockTree ) ;
78+
79+ const mockComponentSet = new ComponentSet ( [ ] , registry ) ;
80+ sandbox . stub ( mockComponentSet , 'getSourceComponents' ) . returns ( { toArray : ( ) => [ ] } as never ) ;
81+ sandbox . stub ( ComponentSet , 'fromSource' ) . returns ( mockComponentSet ) ;
82+
83+
84+ // Create real SourceComponent instances for testing
85+ const allowedComponent = new SourceComponent ( {
86+ name : 'AllowedClass' ,
87+ type : registry . getTypeByName ( 'ApexClass' ) ,
88+ xml : '/test/force-app/main/default/classes/AllowedClass.cls-meta.xml' ,
89+ content : '/test/force-app/main/default/classes/AllowedClass.cls'
90+ } ) ;
91+
92+ const ignoredComponent = new SourceComponent ( {
93+ name : 'IgnoredClass' ,
94+ type : registry . getTypeByName ( 'ApexClass' ) ,
95+ xml : '/test/force-app/main/default/classes/IgnoredClass.cls-meta.xml' ,
96+ content : '/test/force-app/main/default/classes/IgnoredClass.cls'
97+ } ) ;
98+
99+ const noXmlComponent = new SourceComponent ( {
100+ name : 'NoXmlComponent' ,
101+ type : registry . getTypeByName ( 'StaticResource' ) ,
102+ content : '/test/force-app/main/default/staticresources/test.resource'
103+ } ) ;
104+
105+ // Mock getForceIgnore to control filtering behavior with proper typing
106+ sandbox . stub ( allowedComponent , 'getForceIgnore' ) . returns ( {
107+ denies : ( ) => false , // This should NOT be ignored
108+ accepts : ( ) => true
109+ } as unknown as ReturnType < SourceComponent [ 'getForceIgnore' ] > ) ;
110+
111+ sandbox . stub ( ignoredComponent , 'getForceIgnore' ) . returns ( {
112+ denies : ( filePath : string ) => filePath . includes ( 'IgnoredClass' ) , // This SHOULD be ignored
113+ accepts : ( filePath : string ) => ! filePath . includes ( 'IgnoredClass' )
114+ } as unknown as ReturnType < SourceComponent [ 'getForceIgnore' ] > ) ;
115+
116+ sandbox . stub ( noXmlComponent , 'getForceIgnore' ) . returns ( {
117+ denies : ( ) => false ,
118+ accepts : ( ) => true
119+ } as unknown as ReturnType < SourceComponent [ 'getForceIgnore' ] > ) ;
120+
121+ // Mock converter to return test data that will trigger the filtering logic
122+ sandbox . stub ( MetadataConverter . prototype , 'convert' ) . resolves ( {
123+ converted : [ allowedComponent , ignoredComponent , noXmlComponent ] ,
124+ deleted : [ ]
125+ } ) ;
126+
127+
128+ const options : MetadataApiRetrieveOptions = {
129+ output : '/test/output' ,
130+ registry,
131+ merge : false ,
132+ usernameOrConnection : 'test@example.com'
133+ } ;
134+
135+ const result = await extract ( {
136+ zip : mockZipBuffer ,
137+ options,
138+ logger,
139+ } ) ;
140+
141+
142+ expect ( result . componentSet ) . to . be . instanceOf ( ComponentSet ) ;
143+ expect ( result . componentSet . getSourceComponents ( ) . toArray ( ) ) . to . have . length ( 2 ) ;
144+ expect ( result . partialDeleteFileResponses ) . to . be . an ( 'array' ) ;
145+
146+ } ) ;
147+
148+ it ( 'should handle merge operations with forceIgnoredPaths' , async ( ) => {
149+ const mockForceIgnoredPaths = new Set ( [ '/ignored/path1' , '/ignored/path2' ] ) ;
150+ const mainComponents = new ComponentSet ( [ ] , registry ) ;
151+ mainComponents . forceIgnoredPaths = mockForceIgnoredPaths ;
152+
153+ const mockZipBuffer = Buffer . from ( 'mock zip content' ) ;
154+ const mockTree = { } as ZipTreeContainer ;
155+
156+ sandbox . stub ( ZipTreeContainer , 'create' ) . resolves ( mockTree ) ;
157+
158+ const mockComponentSet = new ComponentSet ( [ ] , registry ) ;
159+ sandbox . stub ( mockComponentSet , 'getSourceComponents' ) . returns ( { toArray : ( ) => [ ] } as never ) ;
160+ sandbox . stub ( ComponentSet , 'fromSource' ) . returns ( mockComponentSet ) ;
161+
162+ const convertStub = sandbox . stub ( MetadataConverter . prototype , 'convert' ) . resolves ( {
163+ converted : [ ] ,
164+ deleted : [ ]
165+ } ) ;
166+
167+ const options : MetadataApiRetrieveOptions = {
168+ output : '/test/output' ,
169+ registry,
170+ merge : true ,
171+ usernameOrConnection : 'test@example.com'
172+ } ;
173+
174+ await extract ( {
175+ zip : mockZipBuffer ,
176+ options,
177+ logger,
178+ mainComponents
179+ } ) ;
180+
181+ // Verify that outputConfig was called correctly
182+ expect ( convertStub . calledOnce ) . to . be . true ;
183+ const outputConfig = convertStub . firstCall . args [ 2 ] ;
184+ expect ( outputConfig ) . to . have . property ( 'type' , 'merge' ) ;
185+ } ) ;
186+ } ) ;
187+ } ) ;
0 commit comments