diff --git a/pkgs/shared/common/src/CredentialType.cs b/pkgs/shared/common/src/CredentialType.cs
new file mode 100644
index 00000000..14d99684
--- /dev/null
+++ b/pkgs/shared/common/src/CredentialType.cs
@@ -0,0 +1,17 @@
+namespace LaunchDarkly.Sdk
+{
+ ///
+ /// The type of credential used for the environment.
+ ///
+ public enum CredentialType
+ {
+ ///
+ /// A mobile key credential.
+ ///
+ MobileKey,
+ ///
+ /// An SDK key credential.
+ ///
+ SdkKey
+ }
+}
diff --git a/pkgs/shared/common/src/Integrations/Plugins/ApplicationMetadata.cs b/pkgs/shared/common/src/Integrations/Plugins/ApplicationMetadata.cs
new file mode 100644
index 00000000..4a40c866
--- /dev/null
+++ b/pkgs/shared/common/src/Integrations/Plugins/ApplicationMetadata.cs
@@ -0,0 +1,43 @@
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ ///
+ /// Metadata about the application where the SDK is running.
+ ///
+ public sealed class ApplicationMetadata
+ {
+ ///
+ /// Gets the application identifier.
+ ///
+ public string Id { get; }
+
+ ///
+ /// Gets the application version.
+ ///
+ public string Version { get; }
+
+ ///
+ /// Gets the application name.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the application version name.
+ ///
+ public string VersionName { get; }
+
+ ///
+ /// Initializes a new instance of the class with the specified application ID, version, name, and version name.
+ ///
+ /// The application identifier.
+ /// The application version.
+ /// The application name.
+ /// The application version name.
+ public ApplicationMetadata(string id = null, string version = null, string name = null, string versionName = null)
+ {
+ Id = id;
+ Version = version;
+ Name = name;
+ VersionName = versionName;
+ }
+ }
+}
diff --git a/pkgs/shared/common/src/Integrations/Plugins/EnvironmentMetadata.cs b/pkgs/shared/common/src/Integrations/Plugins/EnvironmentMetadata.cs
new file mode 100644
index 00000000..ae1b0237
--- /dev/null
+++ b/pkgs/shared/common/src/Integrations/Plugins/EnvironmentMetadata.cs
@@ -0,0 +1,43 @@
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ ///
+ /// Metadata about the environment where the SDK is running.
+ ///
+ public sealed class EnvironmentMetadata
+ {
+ ///
+ /// Gets the SDK metadata.
+ ///
+ public SdkMetadata Sdk { get; }
+
+ ///
+ /// Gets the SDK key.
+ ///
+ public string Credential { get; }
+
+ ///
+ /// Gets the type of credential used (e.g., Mobile Key or SDK Key).
+ ///
+ public CredentialType CredentialType { get; }
+
+ ///
+ /// Gets the application metadata.
+ ///
+ public ApplicationMetadata Application { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// the SDK metadata
+ /// the SDK Key or Mobile Key
+ /// the type of credential
+ /// the application metadata
+ public EnvironmentMetadata(SdkMetadata sdkMetadata, string credential, CredentialType credentialType, ApplicationMetadata applicationMetadata)
+ {
+ Sdk = sdkMetadata;
+ Credential = credential;
+ CredentialType = credentialType;
+ Application = applicationMetadata;
+ }
+ }
+}
diff --git a/pkgs/shared/common/src/Integrations/Plugins/PluginBase.cs b/pkgs/shared/common/src/Integrations/Plugins/PluginBase.cs
new file mode 100644
index 00000000..1a205f0b
--- /dev/null
+++ b/pkgs/shared/common/src/Integrations/Plugins/PluginBase.cs
@@ -0,0 +1,50 @@
+using System.Collections.Generic;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ ///
+ /// Abstract base class for extending SDK functionality via plugins.
+ /// Consumers should provide specific implementations of this class with the appropariate
+ /// client and hook types for their use case.
+ /// This class includes default implementations for optional methods, allowing
+ /// LaunchDarkly to expand the list of plugin methods without breaking customer integrations.
+ /// Plugins provide an interface which allows for initialization, access to credentials,
+ /// and hook registration in a single interface.
+ ///
+ /// The type of the LaunchDarkly client (e.g., ILdClient)
+ /// The type of hooks used by this plugin (e.g., Hook)
+ public abstract class PluginBase
+ {
+ ///
+ /// Get metadata about the plugin implementation.
+ ///
+ /// Metadata describing this plugin
+ public PluginMetadata Metadata { get; private set; }
+
+ ///
+ /// Initializes a new instance of the class with the specified name.
+ ///
+ /// The name of the plugin.
+ public PluginBase(string name)
+ {
+ Metadata = new PluginMetadata(name);
+ }
+
+ ///
+ /// Registers the plugin with the specified LaunchDarkly client and environment metadata.
+ ///
+ /// An instance of the LaunchDarkly client to register the plugin with.
+ /// Metadata about the environment.
+ public abstract void Register(TClient client, EnvironmentMetadata metadata);
+
+ ///
+ /// Returns a list of hooks to be registered for the plugin, based on the provided environment metadata.
+ ///
+ /// Metadata about the environment.
+ /// A list of hook instances to be registered.
+ public virtual IList GetHooks(EnvironmentMetadata metadata)
+ {
+ return new List();
+ }
+ }
+}
diff --git a/pkgs/shared/common/src/Integrations/Plugins/PluginExtensions.cs b/pkgs/shared/common/src/Integrations/Plugins/PluginExtensions.cs
new file mode 100644
index 00000000..2b79381e
--- /dev/null
+++ b/pkgs/shared/common/src/Integrations/Plugins/PluginExtensions.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using LaunchDarkly.Logging;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ ///
+ /// Extension methods for plugin registration and hook collection.
+ ///
+ public static class PluginExtensions
+ {
+ ///
+ /// Registers all plugins with the client and environment metadata.
+ ///
+ /// The client type (e.g., ILdClient)
+ /// The hook type (e.g., Hook)
+ /// The client instance to register plugins with
+ /// The collection of plugins to register
+ /// Metadata about the environment
+ /// Logger for error reporting
+ ///
+ /// This method iterates through each plugin in the collection and calls its `Register` method
+ /// to initialize it with the client and environment metadata. It logs any exceptions that occur during
+ /// the registration process, allowing the client to continue functioning even if some plugins fail to register.
+ ///
+ public static void RegisterPlugins(
+ this TClient client,
+ IEnumerable> plugins,
+ EnvironmentMetadata environmentMetadata,
+ Logger logger)
+ {
+ foreach (var plugin in plugins)
+ {
+ try
+ {
+ plugin.Register(client, environmentMetadata);
+ }
+ catch (Exception ex)
+ {
+ logger.Error("Error registering plugin {0}: {1}",
+ plugin.Metadata.Name ?? "unknown", ex);
+ }
+ }
+ }
+
+ ///
+ /// Retrieves all hooks from the specified plugins.
+ ///
+ /// The client type
+ /// The hook type
+ /// The client instance to register plugins with
+ /// The collection of plugins
+ /// Metadata about the environment
+ /// Logger for error reporting
+ /// A list of hooks from all plugins
+ ///
+ /// This method iterates through each plugin in the collection and calls its `GetHooks` method
+ /// to retrieve any hooks the plugin provides. It logs any exceptions that occur during
+ /// the hook retrieval process and continues processing remaining plugins.
+ ///
+ public static List GetPluginHooks(
+ this TClient client,
+ IEnumerable> plugins,
+ EnvironmentMetadata environmentMetadata,
+ Logger logger)
+ {
+ var allHooks = new List();
+ foreach (var plugin in plugins)
+ {
+ try
+ {
+ var pluginHooks = plugin.GetHooks(environmentMetadata);
+ if (pluginHooks != null)
+ {
+ allHooks.AddRange(pluginHooks);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.Error("Error getting hooks from plugin {0}: {1}",
+ plugin.Metadata.Name ?? "unknown", ex);
+ }
+ }
+ return allHooks;
+ }
+ }
+}
diff --git a/pkgs/shared/common/src/Integrations/Plugins/PluginMetadata.cs b/pkgs/shared/common/src/Integrations/Plugins/PluginMetadata.cs
new file mode 100644
index 00000000..6226d8fd
--- /dev/null
+++ b/pkgs/shared/common/src/Integrations/Plugins/PluginMetadata.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ ///
+ /// Metadata about a plugin implementation.
+ ///
+ public sealed class PluginMetadata
+ {
+ ///
+ /// The name of the plugin.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Initializes a new instance of the class with the specified name.
+ ///
+ /// The name of the plugin.
+ public PluginMetadata(string name)
+ {
+ Name = name;
+ }
+ }
+}
diff --git a/pkgs/shared/common/src/Integrations/Plugins/SdkMetadata.cs b/pkgs/shared/common/src/Integrations/Plugins/SdkMetadata.cs
new file mode 100644
index 00000000..7aa4e133
--- /dev/null
+++ b/pkgs/shared/common/src/Integrations/Plugins/SdkMetadata.cs
@@ -0,0 +1,44 @@
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ ///
+ /// Metadata about the SDK itself.
+ ///
+ public sealed class SdkMetadata
+ {
+ ///
+ /// Gets the id of the SDK. This should match the identifier in the SDK.
+ /// This field should be either the x-launchdarkly-user-agent or the user-agent.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the version of the SDK.
+ ///
+ public string Version { get; }
+
+ ///
+ /// Gets the wrapper name if this SDK is a wrapper.
+ ///
+ public string WrapperName { get; }
+
+ ///
+ /// Gets the wrapper version if this SDK is a wrapper.
+ ///
+ public string WrapperVersion { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The id of the SDK. This should match the identifier in the SDK. It should be either the x-launchdarkly-user-agent or the user-agent.
+ /// The version of the SDK.
+ /// If this SDK is a wrapper, then this should be the wrapper name.
+ /// If this SDK is a wrapper, then this should be the wrapper version.
+ public SdkMetadata(string name, string version, string wrapperName = null, string wrapperVersion = null)
+ {
+ Name = name;
+ Version = version;
+ WrapperName = wrapperName;
+ WrapperVersion = wrapperVersion;
+ }
+ }
+}
diff --git a/pkgs/shared/common/test/Integrations/Plugins/ApplicationMetadataTest.cs b/pkgs/shared/common/test/Integrations/Plugins/ApplicationMetadataTest.cs
new file mode 100644
index 00000000..b7e184e6
--- /dev/null
+++ b/pkgs/shared/common/test/Integrations/Plugins/ApplicationMetadataTest.cs
@@ -0,0 +1,29 @@
+using Xunit;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ public class ApplicationMetadataTest
+ {
+ [Fact]
+ public void CanConstructWithAllParameters()
+ {
+ var appMetadata = new ApplicationMetadata("app-id", "1.0.0", "My App", "v1.0.0");
+
+ Assert.Equal("app-id", appMetadata.Id);
+ Assert.Equal("1.0.0", appMetadata.Version);
+ Assert.Equal("My App", appMetadata.Name);
+ Assert.Equal("v1.0.0", appMetadata.VersionName);
+ }
+
+ [Fact]
+ public void CanConstructWithDefaultParameters()
+ {
+ var appMetadata = new ApplicationMetadata();
+
+ Assert.Null(appMetadata.Id);
+ Assert.Null(appMetadata.Version);
+ Assert.Null(appMetadata.Name);
+ Assert.Null(appMetadata.VersionName);
+ }
+ }
+}
diff --git a/pkgs/shared/common/test/Integrations/Plugins/EnvironmentMetadataTest.cs b/pkgs/shared/common/test/Integrations/Plugins/EnvironmentMetadataTest.cs
new file mode 100644
index 00000000..4e1d6c96
--- /dev/null
+++ b/pkgs/shared/common/test/Integrations/Plugins/EnvironmentMetadataTest.cs
@@ -0,0 +1,20 @@
+using Xunit;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ public class EnvironmentMetadataTest
+ {
+ [Fact]
+ public void CanConstructWithAllParameters()
+ {
+ var sdkMetadata = new SdkMetadata("dotnet-server-sdk", "6.0.0");
+ var appMetadata = new ApplicationMetadata("app-id", "1.0.0");
+ var envMetadata = new EnvironmentMetadata(sdkMetadata, "test-sdk-key", CredentialType.SdkKey, appMetadata);
+
+ Assert.Equal(sdkMetadata, envMetadata.Sdk);
+ Assert.Equal("test-sdk-key", envMetadata.Credential);
+ Assert.Equal(CredentialType.SdkKey, envMetadata.CredentialType);
+ Assert.Equal(appMetadata, envMetadata.Application);
+ }
+ }
+}
diff --git a/pkgs/shared/common/test/Integrations/Plugins/PluginBaseTest.cs b/pkgs/shared/common/test/Integrations/Plugins/PluginBaseTest.cs
new file mode 100644
index 00000000..a0a44454
--- /dev/null
+++ b/pkgs/shared/common/test/Integrations/Plugins/PluginBaseTest.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using Xunit;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ public class PluginBaseTest
+ {
+ // Test implementation of PluginBase that uses default GetHooks
+ public class TestPluginWithDefaultHooks : PluginBase
+ {
+ public TestPluginWithDefaultHooks()
+ : base("test-plugin") { }
+
+ public override void Register(string client, EnvironmentMetadata metadata)
+ {
+ // No-op for testing
+ }
+
+ // Uses default GetHooks implementation
+ }
+
+ // Test implementation of PluginBase that overrides GetHooks
+ public class TestPluginWithCustomHooks : PluginBase
+ {
+ public TestPluginWithCustomHooks()
+ : base("test-plugin-with-custom-hooks") { }
+ public List CustomHooks { get; set; } = new List();
+
+ public override void Register(string client, EnvironmentMetadata metadata)
+ {
+ // No-op for testing
+ }
+
+ public override IList GetHooks(EnvironmentMetadata metadata)
+ {
+ return CustomHooks;
+ }
+ }
+
+ [Fact]
+ public void DefaultGetHooks_ReturnsEmptyList()
+ {
+ var plugin = new TestPluginWithDefaultHooks();
+ var metadata = new EnvironmentMetadata(
+ new SdkMetadata("test-sdk", "1.0.0"),
+ "test-key",
+ CredentialType.SdkKey,
+ new ApplicationMetadata("test-app", "1.0.0")
+ );
+
+ var hooks = plugin.GetHooks(metadata);
+
+ Assert.NotNull(hooks);
+ Assert.Empty(hooks);
+ }
+
+ [Fact]
+ public void CustomGetHooks_ReturnsProvidedHooks()
+ {
+ var plugin = new TestPluginWithCustomHooks();
+ plugin.CustomHooks.Add("hook1");
+ plugin.CustomHooks.Add("hook2");
+
+ var metadata = new EnvironmentMetadata(
+ new SdkMetadata("test-sdk", "1.0.0"),
+ "test-key",
+ CredentialType.SdkKey,
+ new ApplicationMetadata("test-app", "1.0.0")
+ );
+
+ var hooks = plugin.GetHooks(metadata);
+
+ Assert.Equal(2, hooks.Count);
+ Assert.Contains("hook1", hooks);
+ Assert.Contains("hook2", hooks);
+ }
+
+ [Fact]
+ public void GetMetadata_ReturnsExpectedMetadata()
+ {
+ var plugin = new TestPluginWithDefaultHooks();
+
+ Assert.Equal("test-plugin", plugin.Metadata.Name);
+ }
+
+ [Fact]
+ public void Register_CanBeCalledWithoutException()
+ {
+ var plugin = new TestPluginWithDefaultHooks();
+ var metadata = new EnvironmentMetadata(
+ new SdkMetadata("test-sdk", "1.0.0"),
+ "test-key",
+ CredentialType.SdkKey,
+ new ApplicationMetadata("test-app", "1.0.0")
+ );
+
+ // Should not throw exception
+ plugin.Register("test-client", metadata);
+ }
+ }
+}
diff --git a/pkgs/shared/common/test/Integrations/Plugins/PluginExtensionsTest.cs b/pkgs/shared/common/test/Integrations/Plugins/PluginExtensionsTest.cs
new file mode 100644
index 00000000..d76b1805
--- /dev/null
+++ b/pkgs/shared/common/test/Integrations/Plugins/PluginExtensionsTest.cs
@@ -0,0 +1,209 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using LaunchDarkly.Logging;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ // Mock client for testing
+ public class MockClient
+ {
+ public List RegisteredPlugins { get; } = new List();
+ }
+
+ // Mock hook for testing
+ public class MockHook
+ {
+ public string Name { get; set; }
+ }
+
+ // Mock plugin implementation for testing
+ public class MockPlugin : PluginBase
+ {
+ public MockPlugin(string name)
+ : base(name) { }
+ public List Hooks { get; set; } = new List();
+ public bool ThrowOnRegister { get; set; } = false;
+ public bool ThrowOnGetHooks { get; set; } = false;
+ public bool ReturnNullHooks { get; set; } = false;
+
+ public override void Register(MockClient client, EnvironmentMetadata metadata)
+ {
+ if (ThrowOnRegister)
+ throw new InvalidOperationException("Test exception");
+
+ client.RegisteredPlugins.Add(Metadata.Name);
+ }
+
+ public override IList GetHooks(EnvironmentMetadata metadata)
+ {
+ if (ThrowOnGetHooks)
+ throw new InvalidOperationException("Test exception");
+
+ if (ReturnNullHooks)
+ return null;
+
+ return Hooks;
+ }
+ }
+
+ public class PluginExtensionsTest
+ {
+ private readonly ITestOutputHelper _testOutputHelper;
+ private readonly Logger _logger;
+ private readonly MockClient _client;
+ private readonly EnvironmentMetadata _environmentMetadata;
+
+ public PluginExtensionsTest(ITestOutputHelper testOutputHelper)
+ {
+ _testOutputHelper = testOutputHelper;
+ _logger = TestLogging.TestLogger(testOutputHelper);
+ _client = new MockClient();
+ _environmentMetadata = new EnvironmentMetadata(
+ new SdkMetadata("test-sdk", "1.0.0"),
+ "test-key",
+ CredentialType.SdkKey,
+ new ApplicationMetadata("test-app", "1.0.0")
+ );
+ }
+
+ [Fact]
+ public void RegisterPlugins_SuccessfullyRegistersAllPlugins()
+ {
+ var plugin1 = new MockPlugin("plugin1");
+ var plugin2 = new MockPlugin("plugin2");
+ var plugins = new List { plugin1, plugin2 };
+
+ _client.RegisterPlugins(plugins, _environmentMetadata, _logger);
+
+ Assert.Contains("plugin1", _client.RegisteredPlugins);
+ Assert.Contains("plugin2", _client.RegisteredPlugins);
+ Assert.Equal(2, _client.RegisteredPlugins.Count);
+ }
+
+ [Fact]
+ public void RegisterPlugins_ContinuesAfterExceptionInOnePlugin()
+ {
+ var plugin1 = new MockPlugin("plugin1") { ThrowOnRegister = true };
+ var plugin2 = new MockPlugin("plugin2");
+ var plugins = new List { plugin1, plugin2 };
+
+ _client.RegisterPlugins(plugins, _environmentMetadata, _logger);
+
+ Assert.DoesNotContain("plugin1", _client.RegisteredPlugins);
+ Assert.Contains("plugin2", _client.RegisteredPlugins);
+ Assert.Single(_client.RegisteredPlugins);
+ }
+
+ [Fact]
+ public void RegisterPlugins_HandlesEmptyPluginsList()
+ {
+ var plugins = new List();
+
+ _client.RegisterPlugins(plugins, _environmentMetadata, _logger);
+
+ Assert.Empty(_client.RegisteredPlugins);
+ }
+
+ [Fact]
+ public void GetPluginHooks_ReturnsAllHooksFromAllPlugins()
+ {
+ var plugin1 = new MockPlugin("plugin1")
+ {
+ Hooks = new List { new MockHook { Name = "hook1" } }
+ };
+ var plugin2 = new MockPlugin("plugin2")
+ {
+ Hooks = new List
+ {
+ new MockHook { Name = "hook2" },
+ new MockHook { Name = "hook3" }
+ }
+ };
+ var plugins = new List { plugin1, plugin2 };
+
+ var hooks = _client.GetPluginHooks(plugins, _environmentMetadata, _logger);
+
+ Assert.Equal(3, hooks.Count);
+ Assert.Contains(hooks, h => h.Name == "hook1");
+ Assert.Contains(hooks, h => h.Name == "hook2");
+ Assert.Contains(hooks, h => h.Name == "hook3");
+ }
+
+ [Fact]
+ public void GetPluginHooks_ContinuesAfterExceptionInOnePlugin()
+ {
+ var plugin1 = new MockPlugin("plugin1")
+ {
+ Hooks = new List { new MockHook { Name = "hook1" } },
+ ThrowOnGetHooks = true
+ };
+ var plugin2 = new MockPlugin("plugin2")
+ {
+ Hooks = new List { new MockHook { Name = "hook2" } }
+ };
+ var plugins = new List { plugin1, plugin2 };
+
+ var hooks = _client.GetPluginHooks(plugins, _environmentMetadata, _logger);
+
+ Assert.Single(hooks);
+ Assert.Contains(hooks, h => h.Name == "hook2");
+ }
+
+ [Fact]
+ public void GetPluginHooks_HandlesNullHooksFromPlugin()
+ {
+ var plugin1 = new MockPlugin("plugin1")
+ {
+ ReturnNullHooks = true
+ };
+ var plugin2 = new MockPlugin("plugin2")
+ {
+ Hooks = new List { new MockHook { Name = "hook2" } }
+ };
+ var plugins = new List { plugin1, plugin2 };
+
+ var hooks = _client.GetPluginHooks(plugins, _environmentMetadata, _logger);
+
+ Assert.Single(hooks);
+ Assert.Contains(hooks, h => h.Name == "hook2");
+ }
+
+ [Fact]
+ public void GetPluginHooks_HandlesEmptyPluginsList()
+ {
+ var plugins = new List();
+
+ var hooks = _client.GetPluginHooks(plugins, _environmentMetadata, _logger);
+
+ Assert.Empty(hooks);
+ }
+
+ [Fact]
+ public void GetPluginHooks_HandlesPluginWithNoHooks()
+ {
+ var plugin = new MockPlugin("plugin1"); // No hooks added
+ var plugins = new List { plugin };
+
+ var hooks = _client.GetPluginHooks(plugins, _environmentMetadata, _logger);
+
+ Assert.Empty(hooks);
+ }
+ }
+
+ // Helper class for test logging
+ public static class TestLogging
+ {
+ public static Logger TestLogger(ITestOutputHelper testOutputHelper) =>
+ Logs.ToMethod(line =>
+ {
+ try
+ {
+ testOutputHelper.WriteLine("LOG OUTPUT >> " + line);
+ }
+ catch { }
+ }).Logger("");
+ }
+}
diff --git a/pkgs/shared/common/test/Integrations/Plugins/PluginMetadataTest.cs b/pkgs/shared/common/test/Integrations/Plugins/PluginMetadataTest.cs
new file mode 100644
index 00000000..790c633c
--- /dev/null
+++ b/pkgs/shared/common/test/Integrations/Plugins/PluginMetadataTest.cs
@@ -0,0 +1,23 @@
+using System;
+using Xunit;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ public class PluginMetadataTest
+ {
+ [Fact]
+ public void CanConstructWithValidName()
+ {
+ var pluginMetadata = new PluginMetadata("test-plugin");
+
+ Assert.Equal("test-plugin", pluginMetadata.Name);
+ }
+
+ [Fact]
+ public void NoExceptionForNullName()
+ {
+ var pluginMetadata = new PluginMetadata(null);
+ Assert.Null(pluginMetadata.Name);
+ }
+ }
+}
diff --git a/pkgs/shared/common/test/Integrations/Plugins/SdkMetadataTest.cs b/pkgs/shared/common/test/Integrations/Plugins/SdkMetadataTest.cs
new file mode 100644
index 00000000..46b4201f
--- /dev/null
+++ b/pkgs/shared/common/test/Integrations/Plugins/SdkMetadataTest.cs
@@ -0,0 +1,30 @@
+using Xunit;
+
+namespace LaunchDarkly.Sdk.Integrations.Plugins
+{
+ public class SdkMetadataTest
+ {
+
+ [Fact]
+ public void CanConstructWithAllParameters()
+ {
+ var sdkMetadata = new SdkMetadata("dotnet-server-sdk", "6.0.0", "my-wrapper", "2.0.0");
+
+ Assert.Equal("dotnet-server-sdk", sdkMetadata.Name);
+ Assert.Equal("6.0.0", sdkMetadata.Version);
+ Assert.Equal("my-wrapper", sdkMetadata.WrapperName);
+ Assert.Equal("2.0.0", sdkMetadata.WrapperVersion);
+ }
+
+ [Fact]
+ public void CanHandleNullValues()
+ {
+ var sdkMetadata = new SdkMetadata(null, null);
+
+ Assert.Null(sdkMetadata.Name);
+ Assert.Null(sdkMetadata.Version);
+ Assert.Null(sdkMetadata.WrapperName);
+ Assert.Null(sdkMetadata.WrapperVersion);
+ }
+ }
+}