1414using Cognite . Extensions ;
1515using Cognite . Extractor . Utils ;
1616using Cognite . ExtractorUtils . Unstable . Configuration ;
17+ using CogniteSdk . Alpha ;
18+ using System . Net . Http . Headers ;
19+ using CogniteSdk ;
20+ using Microsoft . Extensions . Logging ;
21+ using System . IO ;
22+ using Cognite . ExtractorUtils . Unstable . Tasks ;
23+ using ExtractorUtils . Test . unit . Unstable ;
24+ using YamlDotNet . Core ;
25+ using Cognite . Extractor . Common ;
26+ using System . Dynamic ;
27+ using Newtonsoft . Json ;
1728
1829namespace ExtractorUtils . Test . Unit . Unstable
1930{
2031 public class ConnectionConfigTests
2132 {
22- private static int _tokenCounter ;
33+ private int _tokenCounter ;
2334
2435 private readonly ITestOutputHelper _output ;
2536 public ConnectionConfigTests ( ITestOutputHelper output )
2637 {
2738 _output = output ;
2839 }
2940
30- [ Fact ]
31- public async Task TestGetClient ( )
41+ private ConnectionConfig GetConfig ( )
3242 {
33- var config = new ConnectionConfig
43+ return new ConnectionConfig
3444 {
3545 Project = "project" ,
3646 BaseUrl = "https://greenfield.cognitedata.com" ,
47+ Integration = "test-integration" ,
3748 Authentication = new ClientCredentialsConfig
3849 {
3950 ClientId = "someId" ,
@@ -45,6 +56,13 @@ public async Task TestGetClient()
4556 TokenUrl = "http://example.url/token" ,
4657 }
4758 } ;
59+ }
60+
61+ [ Fact ]
62+ public async Task TestGetClient ( )
63+ {
64+ var config = GetConfig ( ) ;
65+ _tokenCounter = 0 ;
4866
4967 var baseCogniteConfig = new BaseCogniteConfig ( ) ;
5068
@@ -57,6 +75,7 @@ public async Task TestGetClient()
5775 services . AddSingleton ( mockFactory . Object ) ;
5876 services . AddTestLogging ( _output ) ;
5977 DestinationUtilsUnstable . AddCogniteClient ( services , "myApp" , null , setLogger : true , setMetrics : true , setHttpClient : true ) ;
78+ services . AddCogniteDestination ( ) ;
6079 using var provider = services . BuildServiceProvider ( ) ;
6180
6281 var auth = provider . GetRequiredService < IAuthenticator > ( ) ;
@@ -68,7 +87,7 @@ public async Task TestGetClient()
6887 }
6988
7089
71- private static Task < HttpResponseMessage > mockAuthSendAsync ( HttpRequestMessage message , CancellationToken token )
90+ private Task < HttpResponseMessage > mockAuthSendAsync ( HttpRequestMessage message , CancellationToken token )
7291 {
7392 // Verify endpoint and method
7493 Assert . Equal ( $@ "http://example.url/token", message . RequestUri . ToString ( ) ) ;
@@ -86,10 +105,205 @@ private static Task<HttpResponseMessage> mockAuthSendAsync(HttpRequestMessage me
86105 {
87106 StatusCode = HttpStatusCode . OK ,
88107 Content = new StringContent ( reply )
89-
90108 } ;
91109
92110 return Task . FromResult ( response ) ;
93111 }
112+
113+ class MyFancyConfig : VersionedConfig
114+ {
115+ public int Foo { get ; set ; }
116+ public string Bar { get ; set ; }
117+
118+ public override void GenerateDefaults ( )
119+ {
120+ }
121+ }
122+
123+ private ( ConfigSource < MyFancyConfig > , DummySink ) GetConfigSource ( string configPath )
124+ {
125+ var config = GetConfig ( ) ;
126+ _tokenCounter = 0 ;
127+
128+ var services = new ServiceCollection ( ) ;
129+ services . AddConfig ( config , typeof ( ConnectionConfig ) ) ;
130+ var mocks = TestUtilities . GetMockedHttpClientFactory ( mockGetConfig ) ;
131+ var mockHttpMessageHandler = mocks . handler ;
132+ var mockFactory = mocks . factory ;
133+ services . AddSingleton ( mockFactory . Object ) ;
134+ services . AddTestLogging ( _output ) ;
135+ DestinationUtilsUnstable . AddCogniteClient ( services , "myApp" , null , setLogger : true , setMetrics : true , setHttpClient : true ) ;
136+ var provider = services . BuildServiceProvider ( ) ;
137+
138+ Directory . CreateDirectory ( configPath ) ;
139+
140+ var configFile = configPath + "/config.yml" ;
141+
142+ var source = new ConfigSource < MyFancyConfig > (
143+ provider . GetRequiredService < Client > ( ) ,
144+ provider . GetRequiredService < ILogger < ConfigSource < MyFancyConfig > > > ( ) ,
145+ "test-integration" ,
146+ configFile ,
147+ true ) ;
148+
149+ var reporter = new DummySink ( ) ;
150+
151+ return ( source , reporter ) ;
152+ }
153+
154+ [ Fact ]
155+ public async Task TestConfigSource ( )
156+ {
157+ var config = GetConfig ( ) ;
158+
159+ var configPath = TestUtils . AlphaNumericPrefix ( "dotnet_extractor_test" ) + "_config" ;
160+ var ( source , reporter ) = GetConfigSource ( configPath ) ;
161+ var configFile = source . ConfigFilePath ;
162+
163+ // Try to load a new config when one doesn't exist.
164+ await Assert . ThrowsAnyAsync < Exception > ( async ( ) => await source . ResolveLocalConfig ( reporter , CancellationToken . None ) ) ;
165+
166+ // Write an invalid local file.
167+ System . IO . File . WriteAllText ( configFile , @"
168+ foo: 123
169+ baz: test
170+ " ) ;
171+ await Assert . ThrowsAsync < ConfigurationException > ( async ( ) => await source . ResolveLocalConfig ( reporter , CancellationToken . None ) ) ;
172+
173+ // 2 start, 2 end.
174+ Assert . Equal ( 4 , reporter . Errors . Count ) ;
175+
176+ await Assert . ThrowsAsync < ConfigurationException > ( async ( ) => await source . ResolveLocalConfig ( reporter , CancellationToken . None ) ) ;
177+ // Nothing has changed, no new errors.
178+ Assert . Equal ( 4 , reporter . Errors . Count ) ;
179+
180+ // Write a valid local file.
181+ System . IO . File . WriteAllText ( configFile , @"
182+ foo: 123
183+ bar: test
184+ " ) ;
185+ var isNew = await source . ResolveLocalConfig ( reporter , CancellationToken . None ) ;
186+ Assert . True ( isNew ) ;
187+
188+ isNew = await source . ResolveLocalConfig ( reporter , CancellationToken . None ) ;
189+ Assert . False ( isNew ) ;
190+ Assert . Equal ( 123 , source . Config . Foo ) ;
191+ Assert . Equal ( "test" , source . Config . Bar ) ;
192+
193+ // Fail to fetch remote config
194+ await Assert . ThrowsAnyAsync < Exception > ( async ( ) => await source . ResolveRemoteConfig ( null , reporter , CancellationToken . None ) ) ;
195+ // Another 2 error reports.
196+ Assert . Equal ( 6 , reporter . Errors . Count ) ;
197+
198+ // Fail to fetch again with the same error.
199+ await Assert . ThrowsAnyAsync < Exception > ( async ( ) => await source . ResolveRemoteConfig ( null , reporter , CancellationToken . None ) ) ;
200+ // No new reports.
201+ Assert . Equal ( 6 , reporter . Errors . Count ) ;
202+
203+ _responseRevision = new ConfigRevision
204+ {
205+ ExternalId = "test-integration" ,
206+ Config = @"
207+ foo: 321
208+ bar: test
209+ " ,
210+ Revision = 1 ,
211+ } ;
212+
213+ Assert . Equal ( 2 , _getConfigCount ) ;
214+
215+ isNew = await source . ResolveRemoteConfig ( null , reporter , CancellationToken . None ) ;
216+ Assert . True ( isNew ) ;
217+ Assert . Equal ( 321 , source . Config . Foo ) ;
218+ Assert . Equal ( "test" , source . Config . Bar ) ;
219+
220+ isNew = await source . ResolveRemoteConfig ( 1 , reporter , CancellationToken . None ) ;
221+ Assert . False ( isNew ) ;
222+ // Only one new request
223+ Assert . Equal ( 3 , _getConfigCount ) ;
224+
225+ Assert . True ( System . IO . File . Exists ( configPath + "/_temp_config.yml" ) ) ;
226+
227+ Directory . Delete ( configPath , true ) ;
228+ }
229+
230+ [ Fact ]
231+ public async Task TestBufferConfigFile ( )
232+ {
233+ var config = GetConfig ( ) ;
234+ var configPath = TestUtils . AlphaNumericPrefix ( "dotnet_extractor_test" ) + "_config" ;
235+ var ( source , reporter ) = GetConfigSource ( configPath ) ;
236+ var configFile = source . ConfigFilePath ;
237+ var bufferFile = configPath + "/_temp_config.yml" ;
238+
239+ var okRevision = new ConfigRevision
240+ {
241+ ExternalId = "test-integration" ,
242+ Config = @"
243+ foo: 321
244+ bar: test
245+ " ,
246+ Revision = 1 ,
247+ } ;
248+ _responseRevision = okRevision ;
249+
250+ var isNew = await source . ResolveRemoteConfig ( null , reporter , CancellationToken . None ) ;
251+ Assert . True ( isNew ) ;
252+ Assert . True ( System . IO . File . Exists ( bufferFile ) ) ;
253+
254+ // We can load the config from the buffer file.
255+ _responseRevision = null ;
256+ isNew = await source . ResolveRemoteConfig ( null , reporter , CancellationToken . None ) ;
257+ Assert . True ( isNew ) ;
258+
259+ // Make the file write protected. Now loading remote config should fail until we delete it.
260+ System . IO . File . SetAttributes ( bufferFile , FileAttributes . ReadOnly ) ;
261+ _responseRevision = okRevision ;
262+ await Assert . ThrowsAnyAsync < Exception > ( async ( ) => await source . ResolveRemoteConfig ( null , reporter , CancellationToken . None ) ) ;
263+
264+ // Delete the buffer file, loading remote config should now fail.
265+ System . IO . File . SetAttributes ( bufferFile , FileAttributes . Normal ) ;
266+ System . IO . File . Delete ( bufferFile ) ;
267+ _responseRevision = null ;
268+ await Assert . ThrowsAnyAsync < Exception > ( async ( ) => await source . ResolveRemoteConfig ( null , reporter , CancellationToken . None ) ) ;
269+
270+ Directory . Delete ( configPath , true ) ;
271+ }
272+
273+ private ConfigRevision _responseRevision ;
274+ private int _getConfigCount ;
275+
276+ private async Task < HttpResponseMessage > mockGetConfig ( HttpRequestMessage message , CancellationToken token )
277+ {
278+ var uri = message . RequestUri . ToString ( ) ;
279+ if ( uri == "http://example.url/token" ) return await mockAuthSendAsync ( message , token ) ;
280+
281+ Assert . Contains ( "/integrations/config" , uri ) ;
282+ _getConfigCount ++ ;
283+
284+ if ( _responseRevision == null )
285+ {
286+ dynamic res = new ExpandoObject ( ) ;
287+ res . error = new ExpandoObject ( ) ;
288+ res . error . code = 400 ;
289+ res . error . message = "Something went wrong" ;
290+ return new HttpResponseMessage
291+ {
292+ StatusCode = HttpStatusCode . BadRequest ,
293+ Content = new StringContent ( JsonConvert . SerializeObject ( res ) ) ,
294+ } ;
295+ }
296+
297+ var resBody = System . Text . Json . JsonSerializer . Serialize ( _responseRevision , Oryx . Cognite . Common . jsonOptions ) ;
298+ var fresponse = new HttpResponseMessage
299+ {
300+ StatusCode = HttpStatusCode . OK ,
301+ Content = new StringContent ( resBody )
302+ } ;
303+ fresponse . Content . Headers . ContentType = new MediaTypeHeaderValue ( "application/json" ) ;
304+ fresponse . Headers . Add ( "x-request-id" , "1" ) ;
305+
306+ return fresponse ;
307+ }
94308 }
95309}
0 commit comments