Skip to content

Commit e474f20

Browse files
committed
Add common helpers to manage configuration and logging
1 parent 21a457c commit e474f20

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Microsoft.Extensions.Configuration;
4+
using Xunit;
5+
6+
namespace Xunit;
7+
8+
public class SecretsFactAttribute : FactAttribute
9+
{
10+
public static IConfiguration Configuration { get; set; } = new ConfigurationBuilder()
11+
.AddEnvironmentVariables()
12+
.AddUserSecrets<SecretsFactAttribute>()
13+
.Build();
14+
15+
public SecretsFactAttribute(params string[] secrets)
16+
{
17+
var missing = new HashSet<string>();
18+
19+
foreach (var secret in secrets)
20+
{
21+
if (string.IsNullOrEmpty(Configuration[secret]))
22+
missing.Add(secret);
23+
}
24+
25+
if (missing.Count > 0)
26+
Skip = "Missing user secrets: " + string.Join(',', missing);
27+
}
28+
}
29+
30+
public class LocalFactAttribute : SecretsFactAttribute
31+
{
32+
public LocalFactAttribute(params string[] secrets) : base(secrets)
33+
{
34+
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
35+
Skip = "Non-CI test";
36+
}
37+
}
38+
39+
public class CIFactAttribute : FactAttribute
40+
{
41+
public CIFactAttribute()
42+
{
43+
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
44+
Skip = "CI-only test";
45+
}
46+
}
47+
48+
public class SecretsTheoryAttribute : TheoryAttribute
49+
{
50+
public SecretsTheoryAttribute(params string[] secrets)
51+
{
52+
var missing = new HashSet<string>();
53+
54+
foreach (var secret in secrets)
55+
{
56+
if (string.IsNullOrEmpty(SecretsFactAttribute.Configuration[secret]))
57+
missing.Add(secret);
58+
}
59+
60+
if (missing.Count > 0)
61+
Skip = "Missing user secrets: " + string.Join(',', missing);
62+
}
63+
}
64+
65+
public class LocalTheoryAttribute : SecretsTheoryAttribute
66+
{
67+
public LocalTheoryAttribute(params string[] secrets) : base(secrets)
68+
{
69+
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
70+
Skip = "Non-CI test";
71+
}
72+
}
73+
74+
public class CITheoryAttribute : SecretsTheoryAttribute
75+
{
76+
public CITheoryAttribute(params string[] secrets) : base(secrets)
77+
{
78+
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
79+
Skip = "CI-only test";
80+
}
81+
}
82+
83+
public class DebuggerFactAttribute : FactAttribute
84+
{
85+
public DebuggerFactAttribute()
86+
{
87+
if (!System.Diagnostics.Debugger.IsAttached)
88+
Skip = "Only running in the debugger";
89+
}
90+
}
91+
92+
public class DebuggerTheoryAttribute : TheoryAttribute
93+
{
94+
public DebuggerTheoryAttribute()
95+
{
96+
if (!System.Diagnostics.Debugger.IsAttached)
97+
Skip = "Only running in the debugger";
98+
}
99+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Options;
10+
11+
public static class ConfigurationExtensions
12+
{
13+
public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
14+
.AddEnvironmentVariables()
15+
.AddUserSecrets(Assembly.GetExecutingAssembly())
16+
.Build();
17+
18+
public static TOptions GetOptions<TOptions>(this IConfiguration configuration, string name)
19+
where TOptions : class, new()
20+
=> new ServiceCollection()
21+
.Configure<TOptions>(configuration.GetSection(name))
22+
.BuildServiceProvider()
23+
.GetRequiredService<IOptions<TOptions>>()
24+
.Value;
25+
26+
public static TOptions GetOptions<TOptions>(this IConfiguration configuration)
27+
where TOptions : class, new()
28+
{
29+
var name = typeof(TOptions).Name;
30+
if (name.EndsWith("Options"))
31+
return configuration.GetOptions<TOptions>(name[..^7]);
32+
33+
return configuration.GetOptions<TOptions>(name);
34+
}
35+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using Microsoft.Extensions.Logging;
7+
8+
public static class LoggerFactoryExtensions
9+
{
10+
public static ILoggerFactory AsLoggerFactory(this ITestOutputHelper output) => new LoggerFactory(output);
11+
}
12+
13+
public class LoggerFactory(ITestOutputHelper output) : ILoggerFactory
14+
{
15+
public ILogger CreateLogger(string categoryName) => new TestOutputLogger(output, categoryName);
16+
public void AddProvider(ILoggerProvider provider) { }
17+
public void Dispose() { }
18+
19+
// create ilogger implementation over testoutputhelper
20+
public class TestOutputLogger(ITestOutputHelper output, string categoryName) : ILogger
21+
{
22+
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null!;
23+
24+
public bool IsEnabled(LogLevel logLevel) => true;
25+
26+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
27+
{
28+
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
29+
if (state == null) throw new ArgumentNullException(nameof(state));
30+
output.WriteLine($"{logLevel}: {categoryName}: {formatter(state, exception)}");
31+
}
32+
}
33+
}

src/AI.Tests/OpenAIOptions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Devlooped.Extensions.AI;
8+
9+
record OpenAIOptions(string Key, string[] Vectors)
10+
{
11+
public static OpenAIOptions Empty { get; } = new();
12+
13+
public OpenAIOptions() : this("", []) { }
14+
}

0 commit comments

Comments
 (0)