-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Expand file tree
/
Copy pathTestEnvironment.cs
More file actions
162 lines (133 loc) · 6.14 KB
/
TestEnvironment.cs
File metadata and controls
162 lines (133 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Testcontainers.CosmosDb;
namespace Microsoft.EntityFrameworkCore.TestUtilities;
#nullable disable
public static class TestEnvironment
{
private static readonly string _emulatorAuthToken =
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
public static IConfiguration Config { get; } = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("cosmosConfig.json", optional: true)
.AddJsonFile("cosmosConfig.test.json", optional: true)
.AddEnvironmentVariables()
.Build()
.GetSection("Test:Cosmos");
private static CosmosDbContainer _container;
private static bool _initialized;
private static readonly SemaphoreSlim _initSemaphore = new(1, 1);
public static string DefaultConnection { get; private set; } = string.IsNullOrEmpty(Config["DefaultConnection"])
? "https://localhost:8081"
: Config["DefaultConnection"];
internal static HttpMessageHandler HttpMessageHandler { get; private set; }
public static async Task InitializeAsync()
{
if (_initialized)
{
return;
}
await _initSemaphore.WaitAsync().ConfigureAwait(false);
try
{
if (_initialized)
{
return;
}
// If a connection string is specified (env var, config.json...), always use that.
var configured = Config["DefaultConnection"];
if (!string.IsNullOrEmpty(configured))
{
DefaultConnection = configured;
_initialized = true;
return;
}
// Try to connect to the default emulator endpoint.
if (await TryProbeEmulatorAsync("https://localhost:8081").ConfigureAwait(false))
{
DefaultConnection = "https://localhost:8081";
_initialized = true;
return;
}
// Try to start a testcontainer with the Linux emulator.
try
{
var container = new CosmosDbBuilder("mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview")
.Build();
await container.StartAsync().ConfigureAwait(false);
_container = container;
AppDomain.CurrentDomain.ProcessExit += (_, _) =>
{
try
{
_container.DisposeAsync().AsTask().GetAwaiter().GetResult();
}
catch
{
// Best-effort cleanup: container may already be stopped or Docker daemon
// may have exited before the process exit handler runs.
}
};
DefaultConnection = new UriBuilder(
Uri.UriSchemeHttp,
_container.Hostname,
_container.GetMappedPublicPort(CosmosDbBuilder.CosmosDbPort)).ToString();
HttpMessageHandler = _container.HttpMessageHandler;
}
catch when (!SkipConnectionCheck)
{
// Any failure (Docker not installed, daemon not running, image pull failure, etc.)
// falls back to the default endpoint. The connection check in CosmosTestStore will
// determine whether the emulator is actually reachable and skip tests if not.
DefaultConnection = "https://localhost:8081";
}
_initialized = true;
}
finally
{
_initSemaphore.Release();
}
}
private static async Task<bool> TryProbeEmulatorAsync(string endpoint)
{
try
{
using var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(3) };
// Any successful response (even 401) means the emulator is up and accepting connections.
using var response = await client.GetAsync(endpoint).ConfigureAwait(false);
return true;
}
catch
{
// Expected: HttpRequestException (connection refused), TaskCanceledException (timeout),
// or SocketException when the emulator is not running.
return false;
}
}
public static string AuthToken { get; } = string.IsNullOrEmpty(Config["AuthToken"])
? _emulatorAuthToken
: Config["AuthToken"];
public static string ConnectionString => $"AccountEndpoint={DefaultConnection};AccountKey={AuthToken}";
public static bool UseTokenCredential { get; } = string.Equals(Config["UseTokenCredential"], "true", StringComparison.OrdinalIgnoreCase);
public static TokenCredential TokenCredential { get; } = new AzureCliCredential(
new AzureCliCredentialOptions { ProcessTimeout = TimeSpan.FromMinutes(5) });
public static string SubscriptionId { get; } = Config["SubscriptionId"];
public static string ResourceGroup { get; } = Config["ResourceGroup"];
public static AzureLocation AzureLocation { get; } = string.IsNullOrEmpty(Config["AzureLocation"])
? AzureLocation.WestUS
: Enum.Parse<AzureLocation>(Config["AzureLocation"]);
public static bool IsEmulator => !UseTokenCredential && (AuthToken == _emulatorAuthToken);
public static bool SkipConnectionCheck { get; } = string.Equals(Config["SkipConnectionCheck"], "true", StringComparison.OrdinalIgnoreCase);
public static string EmulatorType => _container != null
? "linux"
: Config["EmulatorType"] ?? (!OperatingSystem.IsWindows() ? "linux" : "");
public static bool IsLinuxEmulator => IsEmulator
&& EmulatorType.Equals("linux", StringComparison.OrdinalIgnoreCase);
}