Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/actions/contract-tests/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ inputs:
description: 'Github token, used for contract tests'
required: false
default: ''
run_fdv2_tests:
description: 'Whether to run contract tests from the feat/fdv2 branch'
required: false
default: 'false'

runs:
using: composite
Expand Down Expand Up @@ -47,3 +51,29 @@ runs:
test_service_port: 8000
extra_params: '-status-timeout=360 -skip-from=${{ env.SUPPRESSION_FILE }}'
token: ${{ inputs.token }}

- name: Setup Go
if: inputs.run_fdv2_tests == 'true'
uses: actions/setup-go@v5
with:
go-version: '1.21'

- name: Launch Contract Tests FDv2 Flavor
if: inputs.run_fdv2_tests == 'true'
id: launch-contract-tests-fdv2
shell: bash
run: dotnet ${{ inputs.service_dll_file }} > test-service.log 2>&1 & disown

- name: Clone and run contract tests from feat/fdv2 branch
if: inputs.run_fdv2_tests == 'true'
shell: bash
run: |
mkdir -p /tmp/sdk-test-harness
git clone https://github.com/launchdarkly/sdk-test-harness.git /tmp/sdk-test-harness
cp $(dirname ./${{ inputs.service_project_file }})/test-supressions-fdv2.txt /tmp/sdk-test-harness/testharness-suppressions-fdv2.txt
cd /tmp/sdk-test-harness
git checkout feat/fdv2
go build -o test-harness .
./test-harness -url http://localhost:8000 -debug -status-timeout=360 --skip-from=testharness-suppressions-fdv2.txt --stop-service-at-end
env:
GITHUB_TOKEN: ${{ inputs.token }}
1 change: 1 addition & 0 deletions .github/workflows/sdk-server-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
service_project_file: ${{ env.CONTRACT_TEST_PROJECT_FILE}}
service_dll_file: ${{ env.CONTRACT_TEST_DLL_FILE}}
token: ${{ secrets.GITHUB_TOKEN }}
run_fdv2_tests: 'true'

- uses: ./.github/actions/build-docs
with:
Expand Down
121 changes: 121 additions & 0 deletions pkgs/sdk/server/contract-tests/Representations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class SdkConfigParams
public SdkConfigBigSegmentsParams BigSegments { get; set; }
public SdkTagParams Tags { get; set; }
public SdkHookParams Hooks { get; set; }
public SdkConfigDataSystemParams DataSystem { get; set; }
}

public class SdkTagParams
Expand Down Expand Up @@ -88,6 +89,126 @@ public class SdkConfigBigSegmentsParams
public long? UserCacheTimeMs { get; set; }
}

/// <summary>
/// Constants for store mode values.
/// </summary>
public static class StoreMode
{
/// <summary>
/// Read-only mode - the data system will only read from the persistent store.
/// </summary>
public const int Read = 0;

/// <summary>
/// Read-write mode - the data system can read from, and write to, the persistent store.
/// </summary>
public const int ReadWrite = 1;
}

/// <summary>
/// Constants for persistent store type values.
/// </summary>
public static class PersistentStoreType
{
/// <summary>
/// Redis persistent store type.
/// </summary>
public const string Redis = "redis";

/// <summary>
/// DynamoDB persistent store type.
/// </summary>
public const string DynamoDB = "dynamodb";

/// <summary>
/// Consul persistent store type.
/// </summary>
public const string Consul = "consul";
}

/// <summary>
/// Constants for persistent cache mode values.
/// </summary>
public static class PersistentCacheMode
{
/// <summary>
/// Cache disabled mode.
/// </summary>
public const string Off = "off";

/// <summary>
/// Time-to-live cache mode with a specified TTL.
/// </summary>
public const string TTL = "ttl";

/// <summary>
/// Infinite cache mode - cache forever.
/// </summary>
public const string Infinite = "infinite";
}

public class SdkConfigDataSystemParams
{
public SdkConfigDataStoreParams Store { get; set; }
public int? StoreMode { get; set; }
public SdkConfigDataInitializerParams[] Initializers { get; set; }
public SdkConfigSynchronizersParams Synchronizers { get; set; }
public string PayloadFilter { get; set; }
}

public class SdkConfigDataStoreParams
{
public SdkConfigPersistentDataStoreParams PersistentDataStore { get; set; }
}

public class SdkConfigPersistentDataStoreParams
{
public SdkConfigPersistentStoreParams Store { get; set; }
public SdkConfigPersistentCacheParams Cache { get; set; }
}

public class SdkConfigPersistentStoreParams
{
public string Type { get; set; }
public string Prefix { get; set; }
public string DSN { get; set; }
}

public class SdkConfigPersistentCacheParams
{
public string Mode { get; set; }
public int? TTL { get; set; }
}

public class SdkConfigDataInitializerParams
{
public SdkConfigPollingParams Polling { get; set; }
}

public class SdkConfigSynchronizersParams
{
public SdkConfigSynchronizerParams Primary { get; set; }
public SdkConfigSynchronizerParams Secondary { get; set; }
}

public class SdkConfigSynchronizerParams
{
public SdkConfigStreamingParams Streaming { get; set; }
public SdkConfigPollingParams Polling { get; set; }
}

public class SdkConfigPollingParams
{
public Uri BaseUri { get; set; }
public long? PollIntervalMs { get; set; }
}

public class SdkConfigStreamingParams
{
public Uri BaseUri { get; set; }
public long? InitialRetryDelayMs { get; set; }
}

public class CommandParams
{
public string Command { get; set; }
Expand Down
193 changes: 193 additions & 0 deletions pkgs/sdk/server/contract-tests/SdkClientEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
using LaunchDarkly.Sdk.Json;
using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Hooks;
using LaunchDarkly.Sdk.Server.Integrations;
using LaunchDarkly.Sdk.Server.Migrations;
using LaunchDarkly.Sdk.Server.Subsystems;

namespace TestService
{
Expand Down Expand Up @@ -372,9 +374,200 @@ private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapt
builder.Hooks(Components.Hooks(hooks));
}

if (sdkParams.DataSystem != null)
{
var dataSystemBuilder = Components.DataSystem().Custom();

// TODO: re-enable this code in the future and determine which dependencies on persistent stores need to be added to contract test build process
// Configure persistent store if provided
// if (sdkParams.DataSystem.Store?.PersistentDataStore != null)
// {
// var storeConfig = sdkParams.DataSystem.Store.PersistentDataStore;
// var storeType = storeConfig.Store.Type.ToLower();
// IComponentConfigurer<IDataStore> persistentStore = null;

// PersistentDataStoreBuilder persistentStoreBuilder = null;

// switch (storeType)
// {
// case "redis":
// var redisBuilder = Redis.DataStore();
// if (!string.IsNullOrEmpty(storeConfig.Store.DSN))
// {
// redisBuilder.Uri(storeConfig.Store.DSN);
// }
// if (!string.IsNullOrEmpty(storeConfig.Store.Prefix))
// {
// redisBuilder.Prefix(storeConfig.Store.Prefix);
// }
// persistentStoreBuilder = Components.PersistentDataStore(redisBuilder);
// break;

// case "dynamodb":
// // For DynamoDB, DSN is the table name
// var dynamoBuilder = DynamoDB.DataStore(storeConfig.Store.DSN ?? "sdk-contract-tests");
// if (!string.IsNullOrEmpty(storeConfig.Store.Prefix))
// {
// dynamoBuilder.Prefix(storeConfig.Store.Prefix);
// }
// // DynamoDB uses IPersistentDataStoreAsync, so use the async overload
// persistentStoreBuilder = Components.PersistentDataStore(dynamoBuilder);
// break;

// case "consul":
// var consulBuilder = Consul.DataStore();
// if (!string.IsNullOrEmpty(storeConfig.Store.DSN))
// {
// consulBuilder.Address(storeConfig.Store.DSN);
// }
// if (!string.IsNullOrEmpty(storeConfig.Store.Prefix))
// {
// consulBuilder.Prefix(storeConfig.Store.Prefix);
// }
// persistentStoreBuilder = Components.PersistentDataStore(consulBuilder);
// break;
// }

// if (persistentStoreBuilder != null)
// {
// // Configure cache
// var cacheMode = storeConfig.Cache?.Mode?.ToLower();
// if (cacheMode == "off")
// {
// persistentStoreBuilder.NoCaching();
// }
// else if (cacheMode == "ttl" && storeConfig.Cache.TTL.HasValue)
// {
// persistentStoreBuilder.CacheTime(TimeSpan.FromSeconds(storeConfig.Cache.TTL.Value));
// }
// else if (cacheMode == "infinite")
// {
// persistentStoreBuilder.CacheForever();
// }

// // Determine store mode
// var storeMode = sdkParams.DataSystem.StoreMode == 0
// ? DataSystemConfiguration.DataStoreMode.ReadOnly
// : DataSystemConfiguration.DataStoreMode.ReadWrite;

// dataSystemBuilder.PersistentStore(persistentStoreBuilder, storeMode);
// }
// }

// Configure initializers
if (sdkParams.DataSystem.Initializers != null && sdkParams.DataSystem.Initializers.Length > 0)
{
var initializers = new List<IComponentConfigurer<IDataSource>>();
foreach (var initializer in sdkParams.DataSystem.Initializers)
{
if (initializer.Polling != null)
{
var pollingBuilder = DataSystemComponents.Polling();
if (initializer.Polling.BaseUri != null)
{
var endpointOverride = Components.ServiceEndpoints().Polling(initializer.Polling.BaseUri);
pollingBuilder.ServiceEndpointsOverride(endpointOverride);
}
if (initializer.Polling.PollIntervalMs.HasValue)
{
pollingBuilder.PollInterval(TimeSpan.FromMilliseconds(initializer.Polling.PollIntervalMs.Value));
}
if (!string.IsNullOrEmpty(sdkParams.DataSystem.PayloadFilter))
{
// PayloadFilter is not yet supported in FDv2 builders, so we skip it for now
// TODO: Add PayloadFilter support when available
}
initializers.Add(pollingBuilder);
}
}
if (initializers.Count > 0)
{
dataSystemBuilder.Initializers(initializers.ToArray());
}
}

// Configure synchronizers
if (sdkParams.DataSystem.Synchronizers != null)
{
var synchronizers = new List<IComponentConfigurer<IDataSource>>();

// Primary synchronizer
if (sdkParams.DataSystem.Synchronizers.Primary != null)
{
var primary = CreateSynchronizer(sdkParams.DataSystem.Synchronizers.Primary, sdkParams.DataSystem.PayloadFilter);
if (primary != null)
{
synchronizers.Add(primary);
}
}

// Secondary synchronizer (optional)
if (sdkParams.DataSystem.Synchronizers.Secondary != null)
{
var secondary = CreateSynchronizer(sdkParams.DataSystem.Synchronizers.Secondary, sdkParams.DataSystem.PayloadFilter);
if (secondary != null)
{
synchronizers.Add(secondary);
}
}

if (synchronizers.Count > 0)
{
dataSystemBuilder.Synchronizers(synchronizers.ToArray());
}
}

builder.DataSystem(dataSystemBuilder);
}

return builder.Build();
}

private static IComponentConfigurer<IDataSource> CreateSynchronizer(
SdkConfigSynchronizerParams synchronizer,
string payloadFilter)
{
if (synchronizer.Polling != null)
{
var pollingBuilder = DataSystemComponents.Polling();
if (synchronizer.Polling.BaseUri != null)
{
var endpointOverride = Components.ServiceEndpoints().Polling(synchronizer.Polling.BaseUri);
pollingBuilder.ServiceEndpointsOverride(endpointOverride);
}
if (synchronizer.Polling.PollIntervalMs.HasValue)
{
pollingBuilder.PollInterval(TimeSpan.FromMilliseconds(synchronizer.Polling.PollIntervalMs.Value));
}
if (!string.IsNullOrEmpty(payloadFilter))
{
// PayloadFilter is not yet supported in FDv2 builders, so we skip it for now
// TODO: Add PayloadFilter support when available
}
return pollingBuilder;
}
else if (synchronizer.Streaming != null)
{
var streamingBuilder = DataSystemComponents.Streaming();
if (synchronizer.Streaming.BaseUri != null)
{
var endpointOverride = Components.ServiceEndpoints().Streaming(synchronizer.Streaming.BaseUri);
streamingBuilder.ServiceEndpointsOverride(endpointOverride);
}
if (synchronizer.Streaming.InitialRetryDelayMs.HasValue)
{
streamingBuilder.InitialReconnectDelay(TimeSpan.FromMilliseconds(synchronizer.Streaming.InitialRetryDelayMs.Value));
}
if (!string.IsNullOrEmpty(payloadFilter))
{
// PayloadFilter is not yet supported in FDv2 builders, so we skip it for now
// TODO: Add PayloadFilter support when available
}
return streamingBuilder;
}
return null;
}

private MigrationVariationResponse DoMigrationVariation(MigrationVariationParams migrationVariation)
{
var defaultStage = MigrationStageExtensions.FromDataModelString(migrationVariation.DefaultStage);
Expand Down
Loading
Loading