1+ using DG . XrmPluginCore . Enums ;
2+ using DG . XrmPluginCore . Tests . Helpers ;
3+ using DG . XrmPluginCore . Tests . TestCustomApis ;
4+ using FluentAssertions ;
5+ using Microsoft . Xrm . Sdk ;
6+ using NSubstitute ;
7+ using System ;
8+ using System . Linq ;
9+ using System . ServiceModel ;
10+ using Xunit ;
11+
12+ namespace DG . XrmPluginCore . Tests
13+ {
14+ public class CustomAPITests
15+ {
16+ [ Fact ]
17+ public void Execute_NullServiceProvider_ShouldThrowArgumentNullException ( )
18+ {
19+ // Arrange
20+ var customApi = new TestCustomAPI ( ) ;
21+
22+ // Act & Assert
23+ Assert . Throws < ArgumentNullException > ( ( ) => customApi . Execute ( null ) ) ;
24+ }
25+
26+ [ Fact ]
27+ public void Execute_ValidRegistration_ShouldExecuteAction ( )
28+ {
29+ // Arrange
30+ var customApi = new TestCustomAPI ( ) ;
31+ var mockProvider = new MockServiceProvider ( ) ;
32+
33+ // Act
34+ customApi . Execute ( mockProvider . ServiceProvider ) ;
35+
36+ // Assert
37+ customApi . ExecutedAction . Should ( ) . BeTrue ( ) ;
38+ customApi . LastContext . Should ( ) . NotBeNull ( ) ;
39+ }
40+
41+ [ Fact ]
42+ public void Execute_NoRegistration_ShouldNotExecuteAction ( )
43+ {
44+ // Arrange
45+ var customApi = new TestNoRegistrationCustomAPI ( ) ;
46+ var mockProvider = new MockServiceProvider ( ) ;
47+
48+ // Act
49+ customApi . Execute ( mockProvider . ServiceProvider ) ;
50+
51+ // Assert
52+ customApi . ExecutedAction . Should ( ) . BeFalse ( ) ;
53+ }
54+
55+ [ Fact ]
56+ public void Execute_FaultException_ShouldRethrow ( )
57+ {
58+ // Arrange
59+ var mockProvider = new MockServiceProvider ( ) ;
60+ var customApi = new TestCustomAPI ( ) ;
61+
62+ // Setup organization service factory to throw exception when creating organization service
63+ var faultException = new FaultException < OrganizationServiceFault > ( new OrganizationServiceFault ( ) ) ;
64+ mockProvider . OrganizationServiceFactory . CreateOrganizationService ( Arg . Any < Guid ? > ( ) )
65+ . Returns ( x => { throw faultException ; } ) ;
66+
67+ // Act & Assert
68+ var exception = Assert . Throws < FaultException < OrganizationServiceFault > > ( ( ) => customApi . Execute ( mockProvider . ServiceProvider ) ) ;
69+ exception . Should ( ) . Be ( faultException ) ;
70+ }
71+
72+ [ Fact ]
73+ public void RegisterCustomAPI_MultipleRegistrations_ShouldThrowInvalidOperationException ( )
74+ {
75+ // Arrange
76+ var customApi = new TestMultipleRegistrationCustomAPI ( ) ;
77+
78+ // Act & Assert
79+ Assert . Throws < InvalidOperationException > ( ( ) => customApi . TryRegisterSecond ( ) ) ;
80+ }
81+
82+ [ Fact ]
83+ public void GetRegistration_ValidRegistration_ShouldReturnConfiguration ( )
84+ {
85+ // Arrange
86+ var customApi = new TestCustomAPI ( ) ;
87+
88+ // Act
89+ var registration = customApi . GetRegistration ( ) ;
90+
91+ // Assert
92+ registration . Should ( ) . NotBeNull ( ) ;
93+ registration . Name . Should ( ) . Be ( "test_custom_api" ) ;
94+ registration . UniqueName . Should ( ) . Be ( "test_custom_api" ) ;
95+ }
96+
97+ [ Fact ]
98+ public void GetRegistration_WithConfiguration_ShouldReturnFullConfiguration ( )
99+ {
100+ // Arrange
101+ var customApi = new TestCustomAPIWithConfig ( ) ;
102+
103+ // Act
104+ var registration = customApi . GetRegistration ( ) ;
105+
106+ // Assert
107+ registration . Should ( ) . NotBeNull ( ) ;
108+ registration . Name . Should ( ) . Be ( "test_custom_api_with_config" ) ;
109+ registration . Description . Should ( ) . Be ( "Test Custom API" ) ;
110+ registration . IsFunction . Should ( ) . BeTrue ( ) ;
111+ registration . IsPrivate . Should ( ) . BeTrue ( ) ;
112+ registration . EnabledForWorkflow . Should ( ) . BeTrue ( ) ;
113+
114+ var requestParams = registration . RequestParameters . ToList ( ) ;
115+ requestParams . Should ( ) . HaveCount ( 1 ) ;
116+ requestParams [ 0 ] . UniqueName . Should ( ) . Be ( "input_param" ) ;
117+ requestParams [ 0 ] . Type . Should ( ) . Be ( CustomApiParameterType . String ) ;
118+
119+ var responseProps = registration . ResponseProperties . ToList ( ) ;
120+ responseProps . Should ( ) . HaveCount ( 1 ) ;
121+ responseProps [ 0 ] . UniqueName . Should ( ) . Be ( "output_prop" ) ;
122+ responseProps [ 0 ] . Type . Should ( ) . Be ( CustomApiParameterType . String ) ;
123+ }
124+
125+ [ Fact ]
126+ public void OnBeforeConstructLocalPluginContext_ShouldAllowModification ( )
127+ {
128+ // Arrange
129+ var customApi = new TestServiceProviderModificationCustomAPI ( ) ;
130+ var originalProvider = new MockServiceProvider ( ) ;
131+
132+ // Act
133+ customApi . Execute ( originalProvider . ServiceProvider ) ;
134+
135+ // Assert
136+ customApi . ModifiedServiceProviderUsed . Should ( ) . BeTrue ( ) ;
137+ }
138+
139+ [ Fact ]
140+ public void Execute_ShouldTraceEntryAndExit ( )
141+ {
142+ // Arrange
143+ var customApi = new TestCustomAPI ( ) ;
144+ var mockProvider = new MockServiceProvider ( ) ;
145+
146+ // Act
147+ customApi . Execute ( mockProvider . ServiceProvider ) ;
148+
149+ // Assert
150+ mockProvider . TracingService . Received ( ) . Trace (
151+ "{0}, Correlation Id: {1}, Initiating User: {2}" ,
152+ Arg . Is < string > ( s => s . Contains ( "Entered" ) && s . Contains ( "Execute" ) ) ,
153+ Arg . Any < Guid > ( ) ,
154+ Arg . Any < Guid > ( ) ) ;
155+ mockProvider . TracingService . Received ( ) . Trace (
156+ "{0}, Correlation Id: {1}, Initiating User: {2}" ,
157+ Arg . Is < string > ( s => s . Contains ( "Exiting" ) && s . Contains ( "Execute" ) ) ,
158+ Arg . Any < Guid > ( ) ,
159+ Arg . Any < Guid > ( ) ) ;
160+ }
161+
162+ [ Fact ]
163+ public void Execute_ShouldTraceStage ( )
164+ {
165+ // Arrange
166+ var customApi = new TestCustomAPI ( ) ;
167+ var mockProvider = new MockServiceProvider ( ) ;
168+ var stage = 30 ;
169+ mockProvider . PluginExecutionContext . Stage . Returns ( stage ) ;
170+
171+ // Act
172+ customApi . Execute ( mockProvider . ServiceProvider ) ;
173+
174+ // Assert
175+ mockProvider . TracingService . Received ( ) . Trace (
176+ "{0}, Correlation Id: {1}, Initiating User: {2}" ,
177+ stage . ToString ( ) ,
178+ Arg . Any < Guid > ( ) ,
179+ Arg . Any < Guid > ( ) ) ;
180+ }
181+
182+ [ Fact ]
183+ public void Execute_ShouldTraceExecutionInfo ( )
184+ {
185+ // Arrange
186+ var customApi = new TestCustomAPI ( ) ;
187+ var mockProvider = new MockServiceProvider ( ) ;
188+ var entityName = "test_entity" ;
189+ var messageName = "test_message" ;
190+
191+ mockProvider . PluginExecutionContext . PrimaryEntityName . Returns ( entityName ) ;
192+ mockProvider . PluginExecutionContext . MessageName . Returns ( messageName ) ;
193+
194+ // Act
195+ customApi . Execute ( mockProvider . ServiceProvider ) ;
196+
197+ // Assert
198+ mockProvider . TracingService . Received ( ) . Trace (
199+ "{0}, Correlation Id: {1}, Initiating User: {2}" ,
200+ Arg . Is < string > ( s => s . Contains ( entityName ) && s . Contains ( messageName ) && s . Contains ( "is firing for" ) ) ,
201+ Arg . Any < Guid > ( ) ,
202+ Arg . Any < Guid > ( ) ) ;
203+ }
204+ }
205+
206+ // Helper custom API for testing service provider modification
207+ public class TestServiceProviderModificationCustomAPI : CustomAPI
208+ {
209+ public bool ModifiedServiceProviderUsed { get ; private set ; }
210+
211+ public TestServiceProviderModificationCustomAPI ( )
212+ {
213+ RegisterCustomAPI ( "test_modification_api" , Execute ) ;
214+ }
215+
216+ protected override IServiceProvider OnBeforeConstructLocalPluginContext ( IServiceProvider serviceProvider )
217+ {
218+ // Create a wrapper that marks when it's used
219+ var wrapper = Substitute . For < IServiceProvider > ( ) ;
220+ wrapper . GetService ( Arg . Any < Type > ( ) ) . Returns ( callInfo =>
221+ {
222+ ModifiedServiceProviderUsed = true ;
223+ return serviceProvider . GetService ( callInfo . Arg < Type > ( ) ) ;
224+ } ) ;
225+
226+ return wrapper ;
227+ }
228+
229+ private void Execute ( LocalPluginContext context )
230+ {
231+ // Action implementation
232+ }
233+ }
234+ }
0 commit comments