Skip to content

Commit 9dd7437

Browse files
authored
Merge branch 'main' into vnextemulator
2 parents a654f9d + 6b20d7a commit 9dd7437

11 files changed

Lines changed: 329 additions & 8 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Release Binaries ${{ github.event.inputs.release-tag }}
1+
name: Release desktop binaries
22
on:
33
workflow_dispatch:
44
inputs:

ExampleConfigs.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,26 @@
202202
]
203203
}
204204
```
205+
206+
## JSON to Cosmos-NoSQL (Using Authenticated Proxy)
207+
208+
```json
209+
{
210+
"Source": "json",
211+
"Sink": "cosmos-nosql",
212+
"SourceSettings": {
213+
"FilePath": "c:\\data\\sales-data.json"
214+
},
215+
"SinkSettings": {
216+
"ConnectionString": "AccountEndpoint=https://...",
217+
"Database": "myDb",
218+
"Container": "myContainer",
219+
"PartitionKeyPath": "/id",
220+
"WriteMode": "Insert",
221+
"WebProxy": "http://yourproxy.server.com/",
222+
"UseDefaultProxyCredentials": true,
223+
"UseDefaultCredentials": true,
224+
"PreAuthenticate": true
225+
}
226+
}
227+
```

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension.UnitTests/CosmosDataSinkExtensionTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,80 @@
11
using Cosmos.DataTransfer.Interfaces;
22
using System.Dynamic;
3+
using System.Text;
34
using Microsoft.VisualStudio.TestTools.UnitTesting;
5+
using Newtonsoft.Json;
46

57
namespace Cosmos.DataTransfer.CosmosExtension.UnitTests
68
{
79
[TestClass]
810
public class CosmosDataSinkExtensionTests
911
{
12+
[TestMethod]
13+
public void CreateItemStream_WithDateString_PreservesFormat()
14+
{
15+
// Arrange - Simulate data read from source with ISO-8601 date string
16+
var sourceSettings = RawJsonCosmosSerializer.GetDefaultSettings();
17+
var sourceJson = "{\"id\": \"1\", \"event_time\": \"2023-12-19T00:00:00.000Z\"}";
18+
var serializer = JsonSerializer.Create(sourceSettings);
19+
using var reader = new JsonTextReader(new StringReader(sourceJson));
20+
var sourceDict = serializer.Deserialize<Dictionary<string, object?>>(reader)!;
21+
22+
// Act - Create data item and build expando object (simulating the pipeline)
23+
var dataItem = new CosmosDictionaryDataItem(sourceDict);
24+
var expando = dataItem.BuildDynamicObjectTree()!;
25+
26+
// Serialize using the same settings that CreateItemStream uses
27+
var json = JsonConvert.SerializeObject(expando, RawJsonCosmosSerializer.GetDefaultSettings());
28+
29+
// Assert - The date string format should be preserved
30+
Assert.IsTrue(json.Contains("\"2023-12-19T00:00:00.000Z\""),
31+
$"Date format should be preserved. Actual JSON: {json}");
32+
}
33+
34+
[TestMethod]
35+
public void CreateItemStream_WithNestedDateString_PreservesFormat()
36+
{
37+
// Arrange - Simulate data with nested date strings
38+
var sourceSettings = RawJsonCosmosSerializer.GetDefaultSettings();
39+
var sourceJson = "{\"id\": \"1\", \"data\": {\"created\": \"2023-12-19T00:00:00.000Z\", \"modified\": \"2023-12-20T12:30:45.123Z\"}}";
40+
var serializer = JsonSerializer.Create(sourceSettings);
41+
using var reader = new JsonTextReader(new StringReader(sourceJson));
42+
var sourceDict = serializer.Deserialize<Dictionary<string, object?>>(reader)!;
43+
44+
// Act
45+
var dataItem = new CosmosDictionaryDataItem(sourceDict);
46+
var expando = dataItem.BuildDynamicObjectTree()!;
47+
var json = JsonConvert.SerializeObject(expando, RawJsonCosmosSerializer.GetDefaultSettings());
48+
49+
// Assert
50+
Assert.IsTrue(json.Contains("\"2023-12-19T00:00:00.000Z\""),
51+
$"Created date format should be preserved. Actual JSON: {json}");
52+
Assert.IsTrue(json.Contains("\"2023-12-20T12:30:45.123Z\""),
53+
$"Modified date format should be preserved. Actual JSON: {json}");
54+
}
55+
56+
[TestMethod]
57+
public void CreateItemStream_WithDateStringArray_PreservesFormat()
58+
{
59+
// Arrange - Simulate data with date strings in array
60+
var sourceSettings = RawJsonCosmosSerializer.GetDefaultSettings();
61+
var sourceJson = "{\"id\": \"1\", \"timestamps\": [\"2023-12-19T00:00:00.000Z\", \"2023-12-20T00:00:00.000Z\"]}";
62+
var serializer = JsonSerializer.Create(sourceSettings);
63+
using var reader = new JsonTextReader(new StringReader(sourceJson));
64+
var sourceDict = serializer.Deserialize<Dictionary<string, object?>>(reader)!;
65+
66+
// Act
67+
var dataItem = new CosmosDictionaryDataItem(sourceDict);
68+
var expando = dataItem.BuildDynamicObjectTree()!;
69+
var json = JsonConvert.SerializeObject(expando, RawJsonCosmosSerializer.GetDefaultSettings());
70+
71+
// Assert
72+
Assert.IsTrue(json.Contains("\"2023-12-19T00:00:00.000Z\""),
73+
$"First date format should be preserved. Actual JSON: {json}");
74+
Assert.IsTrue(json.Contains("\"2023-12-20T00:00:00.000Z\""),
75+
$"Second date format should be preserved. Actual JSON: {json}");
76+
}
77+
1078
[TestMethod]
1179
public void BuildDynamicObjectTree_WithNestedArrays_WorksCorrectly()
1280
{

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension.UnitTests/CosmosSinkSettingsTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,85 @@ public void GetValidationErrors_WhenRbacWithoutInitClientEncryption_Succeeds()
209209

210210
Assert.AreEqual(0, validationErrors.Count());
211211
}
212+
213+
[TestMethod]
214+
public void UseDefaultProxyCredentials_DefaultsToFalse()
215+
{
216+
var settings = new CosmosSinkSettings
217+
{
218+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
219+
Database = "db",
220+
Container = "container",
221+
};
222+
223+
Assert.IsFalse(settings.UseDefaultProxyCredentials);
224+
}
225+
226+
[TestMethod]
227+
public void UseDefaultProxyCredentials_CanBeSetToTrue()
228+
{
229+
var settings = new CosmosSinkSettings
230+
{
231+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
232+
Database = "db",
233+
Container = "container",
234+
UseDefaultProxyCredentials = true,
235+
};
236+
237+
Assert.IsTrue(settings.UseDefaultProxyCredentials);
238+
}
239+
240+
[TestMethod]
241+
public void UseDefaultCredentials_DefaultsToFalse()
242+
{
243+
var settings = new CosmosSinkSettings
244+
{
245+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
246+
Database = "db",
247+
Container = "container",
248+
};
249+
250+
Assert.IsFalse(settings.UseDefaultCredentials);
251+
}
252+
253+
[TestMethod]
254+
public void UseDefaultCredentials_CanBeSetToTrue()
255+
{
256+
var settings = new CosmosSinkSettings
257+
{
258+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
259+
Database = "db",
260+
Container = "container",
261+
UseDefaultCredentials = true,
262+
};
263+
264+
Assert.IsTrue(settings.UseDefaultCredentials);
265+
}
266+
267+
[TestMethod]
268+
public void PreAuthenticate_DefaultsToFalse()
269+
{
270+
var settings = new CosmosSinkSettings
271+
{
272+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
273+
Database = "db",
274+
Container = "container",
275+
};
276+
277+
Assert.IsFalse(settings.PreAuthenticate);
278+
}
279+
280+
[TestMethod]
281+
public void PreAuthenticate_CanBeSetToTrue()
282+
{
283+
var settings = new CosmosSinkSettings
284+
{
285+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
286+
Database = "db",
287+
Container = "container",
288+
PreAuthenticate = true,
289+
};
290+
291+
Assert.IsTrue(settings.PreAuthenticate);
292+
}
212293
}

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension.UnitTests/CosmosSourceSettingsTests.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,85 @@ public void Validate_WithAccountEndpoint_Succeeds()
6060

6161
settings.Validate();
6262
}
63+
64+
[TestMethod]
65+
public void UseDefaultProxyCredentials_DefaultsToFalse()
66+
{
67+
var settings = new CosmosSourceSettings
68+
{
69+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
70+
Database = "db",
71+
Container = "container",
72+
};
73+
74+
Assert.IsFalse(settings.UseDefaultProxyCredentials);
75+
}
76+
77+
[TestMethod]
78+
public void UseDefaultProxyCredentials_CanBeSetToTrue()
79+
{
80+
var settings = new CosmosSourceSettings
81+
{
82+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
83+
Database = "db",
84+
Container = "container",
85+
UseDefaultProxyCredentials = true,
86+
};
87+
88+
Assert.IsTrue(settings.UseDefaultProxyCredentials);
89+
}
90+
91+
[TestMethod]
92+
public void UseDefaultCredentials_DefaultsToFalse()
93+
{
94+
var settings = new CosmosSourceSettings
95+
{
96+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
97+
Database = "db",
98+
Container = "container",
99+
};
100+
101+
Assert.IsFalse(settings.UseDefaultCredentials);
102+
}
103+
104+
[TestMethod]
105+
public void UseDefaultCredentials_CanBeSetToTrue()
106+
{
107+
var settings = new CosmosSourceSettings
108+
{
109+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
110+
Database = "db",
111+
Container = "container",
112+
UseDefaultCredentials = true,
113+
};
114+
115+
Assert.IsTrue(settings.UseDefaultCredentials);
116+
}
117+
118+
[TestMethod]
119+
public void PreAuthenticate_DefaultsToFalse()
120+
{
121+
var settings = new CosmosSourceSettings
122+
{
123+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
124+
Database = "db",
125+
Container = "container",
126+
};
127+
128+
Assert.IsFalse(settings.PreAuthenticate);
129+
}
130+
131+
[TestMethod]
132+
public void PreAuthenticate_CanBeSetToTrue()
133+
{
134+
var settings = new CosmosSourceSettings
135+
{
136+
ConnectionString = "AccountEndpoint=https://localhost:8081/;AccountKey=",
137+
Database = "db",
138+
Container = "container",
139+
PreAuthenticate = true,
140+
};
141+
142+
Assert.IsTrue(settings.PreAuthenticate);
143+
}
63144
}

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosDataSinkExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ private static async Task<ItemResult> PopulateItem(Container container, ExpandoO
280280

281281
private static MemoryStream CreateItemStream(ExpandoObject item)
282282
{
283-
var json = JsonConvert.SerializeObject(item);
283+
var json = JsonConvert.SerializeObject(item, RawJsonCosmosSerializer.GetDefaultSettings());
284284
return new MemoryStream(Encoding.UTF8.GetBytes(json));
285285
}
286286

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosExtensionServices.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,36 @@
66
using Microsoft.Extensions.Logging;
77
using System.Globalization;
88
using System.Net;
9+
using System.Net.Http;
910
using System.Reflection;
1011
using System.Text.RegularExpressions;
1112

1213
namespace Cosmos.DataTransfer.CosmosExtension
1314
{
1415
public static class CosmosExtensionServices
1516
{
17+
// Static HttpClient instances with different configurations for reuse across connections
18+
// This avoids connection exhaustion and properly handles credentials
19+
private static readonly Lazy<HttpClient> _httpClientWithDefaultCredentials = new Lazy<HttpClient>(() =>
20+
{
21+
var handler = new HttpClientHandler
22+
{
23+
Credentials = CredentialCache.DefaultNetworkCredentials,
24+
PreAuthenticate = false
25+
};
26+
return new HttpClient(handler);
27+
});
28+
29+
private static readonly Lazy<HttpClient> _httpClientWithDefaultCredentialsAndPreAuth = new Lazy<HttpClient>(() =>
30+
{
31+
var handler = new HttpClientHandler
32+
{
33+
Credentials = CredentialCache.DefaultNetworkCredentials,
34+
PreAuthenticate = true
35+
};
36+
return new HttpClient(handler);
37+
});
38+
1639
public static CosmosClient CreateClient(CosmosSettingsBase settings, string displayName, ILogger logger, string? sourceDisplayName = null)
1740
{
1841
string userAgentString = CreateUserAgentString(displayName, sourceDisplayName);
@@ -36,7 +59,21 @@ public static CosmosClient CreateClient(CosmosSettingsBase settings, string disp
3659
};
3760

3861
if (!string.IsNullOrEmpty(settings.WebProxy)){
39-
clientOptions.WebProxy = new WebProxy(settings.WebProxy);
62+
var webProxy = new WebProxy(settings.WebProxy);
63+
if (settings.UseDefaultProxyCredentials)
64+
{
65+
webProxy.UseDefaultCredentials = true;
66+
}
67+
clientOptions.WebProxy = webProxy;
68+
}
69+
70+
// Configure the HttpClient with default credentials if requested
71+
// This enables authenticated proxy support for the underlying HTTP connections
72+
if (settings.UseDefaultCredentials)
73+
{
74+
clientOptions.HttpClientFactory = settings.PreAuthenticate
75+
? () => _httpClientWithDefaultCredentialsAndPreAuth.Value
76+
: () => _httpClientWithDefaultCredentials.Value;
4077
}
4178

4279
// Disable SSL certificate validation for development scenarios

Extensions/Cosmos/Cosmos.DataTransfer.CosmosExtension/CosmosSettingsBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public abstract class CosmosSettingsBase : IValidatableObject
1212
public string? Container { get; set; }
1313
public ConnectionMode ConnectionMode { get; set; } = ConnectionMode.Gateway;
1414
public string? WebProxy { get; set; }
15+
public bool UseDefaultProxyCredentials { get; set; } = false;
16+
public bool UseDefaultCredentials { get; set; } = false;
17+
public bool PreAuthenticate { get; set; } = false;
1518
public bool UseRbacAuth { get; set; }
1619
public string? AccountEndpoint { get; set; }
1720
public bool EnableInteractiveCredentials { get; set; }

0 commit comments

Comments
 (0)