Skip to content

Commit 892882e

Browse files
committed
chore: updates contract tests CI to also test FDv2 cases
1 parent adaa4fa commit 892882e

File tree

6 files changed

+334
-0
lines changed

6 files changed

+334
-0
lines changed

.github/actions/contract-tests/action.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ inputs:
1111
description: 'Github token, used for contract tests'
1212
required: false
1313
default: ''
14+
run_fdv2_tests:
15+
description: 'Whether to run contract tests from the feat/fdv2 branch'
16+
required: false
17+
default: 'false'
1418

1519
runs:
1620
using: composite
@@ -47,3 +51,17 @@ runs:
4751
test_service_port: 8000
4852
extra_params: '-status-timeout=360 -skip-from=${{ env.SUPPRESSION_FILE }}'
4953
token: ${{ inputs.token }}
54+
55+
- name: Clone and run contract tests from feat/fdv2 branch
56+
if: inputs.run_fdv2_tests == 'true'
57+
shell: bash
58+
run: |
59+
mkdir -p /tmp/sdk-test-harness
60+
git clone https://github.com/launchdarkly/sdk-test-harness.git /tmp/sdk-test-harness
61+
cp ${{ env.SUPPRESSION_FILE }} /tmp/sdk-test-harness/testharness-suppressions-fdv2.txt
62+
cd /tmp/sdk-test-harness
63+
git checkout feat/fdv2
64+
go build -o test-harness .
65+
./test-harness -url http://localhost:8000 -debug --skip-from=testharness-suppressions-fdv2.txt
66+
env:
67+
GITHUB_TOKEN: ${{ inputs.token }}

.github/workflows/sdk-server-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
service_project_file: ${{ env.CONTRACT_TEST_PROJECT_FILE}}
3737
service_dll_file: ${{ env.CONTRACT_TEST_DLL_FILE}}
3838
token: ${{ secrets.GITHUB_TOKEN }}
39+
run_fdv2_tests: 'true'
3940

4041
- uses: ./.github/actions/build-docs
4142
with:

pkgs/sdk/server/contract-tests/Representations.cs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class SdkConfigParams
3131
public SdkConfigBigSegmentsParams BigSegments { get; set; }
3232
public SdkTagParams Tags { get; set; }
3333
public SdkHookParams Hooks { get; set; }
34+
public SdkConfigDataSystemParams DataSystem { get; set; }
3435
}
3536

3637
public class SdkTagParams
@@ -88,6 +89,126 @@ public class SdkConfigBigSegmentsParams
8889
public long? UserCacheTimeMs { get; set; }
8990
}
9091

92+
/// <summary>
93+
/// Constants for store mode values.
94+
/// </summary>
95+
public static class StoreMode
96+
{
97+
/// <summary>
98+
/// Read-only mode - the data system will only read from the persistent store.
99+
/// </summary>
100+
public const int Read = 0;
101+
102+
/// <summary>
103+
/// Read-write mode - the data system can read from, and write to, the persistent store.
104+
/// </summary>
105+
public const int ReadWrite = 1;
106+
}
107+
108+
/// <summary>
109+
/// Constants for persistent store type values.
110+
/// </summary>
111+
public static class PersistentStoreType
112+
{
113+
/// <summary>
114+
/// Redis persistent store type.
115+
/// </summary>
116+
public const string Redis = "redis";
117+
118+
/// <summary>
119+
/// DynamoDB persistent store type.
120+
/// </summary>
121+
public const string DynamoDB = "dynamodb";
122+
123+
/// <summary>
124+
/// Consul persistent store type.
125+
/// </summary>
126+
public const string Consul = "consul";
127+
}
128+
129+
/// <summary>
130+
/// Constants for persistent cache mode values.
131+
/// </summary>
132+
public static class PersistentCacheMode
133+
{
134+
/// <summary>
135+
/// Cache disabled mode.
136+
/// </summary>
137+
public const string Off = "off";
138+
139+
/// <summary>
140+
/// Time-to-live cache mode with a specified TTL.
141+
/// </summary>
142+
public const string TTL = "ttl";
143+
144+
/// <summary>
145+
/// Infinite cache mode - cache forever.
146+
/// </summary>
147+
public const string Infinite = "infinite";
148+
}
149+
150+
public class SdkConfigDataSystemParams
151+
{
152+
public SdkConfigDataStoreParams Store { get; set; }
153+
public int? StoreMode { get; set; }
154+
public SdkConfigDataInitializerParams[] Initializers { get; set; }
155+
public SdkConfigSynchronizersParams Synchronizers { get; set; }
156+
public string PayloadFilter { get; set; }
157+
}
158+
159+
public class SdkConfigDataStoreParams
160+
{
161+
public SdkConfigPersistentDataStoreParams PersistentDataStore { get; set; }
162+
}
163+
164+
public class SdkConfigPersistentDataStoreParams
165+
{
166+
public SdkConfigPersistentStoreParams Store { get; set; }
167+
public SdkConfigPersistentCacheParams Cache { get; set; }
168+
}
169+
170+
public class SdkConfigPersistentStoreParams
171+
{
172+
public string Type { get; set; }
173+
public string Prefix { get; set; }
174+
public string DSN { get; set; }
175+
}
176+
177+
public class SdkConfigPersistentCacheParams
178+
{
179+
public string Mode { get; set; }
180+
public int? TTL { get; set; }
181+
}
182+
183+
public class SdkConfigDataInitializerParams
184+
{
185+
public SdkConfigPollingParams Polling { get; set; }
186+
}
187+
188+
public class SdkConfigSynchronizersParams
189+
{
190+
public SdkConfigSynchronizerParams Primary { get; set; }
191+
public SdkConfigSynchronizerParams Secondary { get; set; }
192+
}
193+
194+
public class SdkConfigSynchronizerParams
195+
{
196+
public SdkConfigStreamingParams Streaming { get; set; }
197+
public SdkConfigPollingParams Polling { get; set; }
198+
}
199+
200+
public class SdkConfigPollingParams
201+
{
202+
public Uri BaseUri { get; set; }
203+
public long? PollIntervalMs { get; set; }
204+
}
205+
206+
public class SdkConfigStreamingParams
207+
{
208+
public Uri BaseUri { get; set; }
209+
public long? InitialRetryDelayMs { get; set; }
210+
}
211+
91212
public class CommandParams
92213
{
93214
public string Command { get; set; }

pkgs/sdk/server/contract-tests/SdkClientEntity.cs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using LaunchDarkly.Sdk.Json;
88
using LaunchDarkly.Sdk.Server;
99
using LaunchDarkly.Sdk.Server.Hooks;
10+
using LaunchDarkly.Sdk.Server.Integrations;
1011
using LaunchDarkly.Sdk.Server.Migrations;
12+
using LaunchDarkly.Sdk.Server.Subsystems;
1113

1214
namespace TestService
1315
{
@@ -372,9 +374,198 @@ private static Configuration BuildSdkConfig(SdkConfigParams sdkParams, ILogAdapt
372374
builder.Hooks(Components.Hooks(hooks));
373375
}
374376

377+
if (sdkParams.DataSystem != null)
378+
{
379+
var dataSystemBuilder = Components.DataSystem().Custom();
380+
381+
// TODO: re-enable this code in the future and determine which dependencies on persistent stores need to be added to contract test build process
382+
// Configure persistent store if provided
383+
// if (sdkParams.DataSystem.Store?.PersistentDataStore != null)
384+
// {
385+
// var storeConfig = sdkParams.DataSystem.Store.PersistentDataStore;
386+
// var storeType = storeConfig.Store.Type.ToLower();
387+
// IComponentConfigurer<IDataStore> persistentStore = null;
388+
389+
// PersistentDataStoreBuilder persistentStoreBuilder = null;
390+
391+
// switch (storeType)
392+
// {
393+
// case "redis":
394+
// var redisBuilder = Redis.DataStore();
395+
// if (!string.IsNullOrEmpty(storeConfig.Store.DSN))
396+
// {
397+
// redisBuilder.Uri(storeConfig.Store.DSN);
398+
// }
399+
// if (!string.IsNullOrEmpty(storeConfig.Store.Prefix))
400+
// {
401+
// redisBuilder.Prefix(storeConfig.Store.Prefix);
402+
// }
403+
// persistentStoreBuilder = Components.PersistentDataStore(redisBuilder);
404+
// break;
405+
406+
// case "dynamodb":
407+
// // For DynamoDB, DSN is the table name
408+
// var dynamoBuilder = DynamoDB.DataStore(storeConfig.Store.DSN ?? "sdk-contract-tests");
409+
// if (!string.IsNullOrEmpty(storeConfig.Store.Prefix))
410+
// {
411+
// dynamoBuilder.Prefix(storeConfig.Store.Prefix);
412+
// }
413+
// // DynamoDB uses IPersistentDataStoreAsync, so use the async overload
414+
// persistentStoreBuilder = Components.PersistentDataStore(dynamoBuilder);
415+
// break;
416+
417+
// case "consul":
418+
// var consulBuilder = Consul.DataStore();
419+
// if (!string.IsNullOrEmpty(storeConfig.Store.DSN))
420+
// {
421+
// consulBuilder.Address(storeConfig.Store.DSN);
422+
// }
423+
// if (!string.IsNullOrEmpty(storeConfig.Store.Prefix))
424+
// {
425+
// consulBuilder.Prefix(storeConfig.Store.Prefix);
426+
// }
427+
// persistentStoreBuilder = Components.PersistentDataStore(consulBuilder);
428+
// break;
429+
// }
430+
431+
// if (persistentStoreBuilder != null)
432+
// {
433+
// // Configure cache
434+
// var cacheMode = storeConfig.Cache?.Mode?.ToLower();
435+
// if (cacheMode == "off")
436+
// {
437+
// persistentStoreBuilder.NoCaching();
438+
// }
439+
// else if (cacheMode == "ttl" && storeConfig.Cache.TTL.HasValue)
440+
// {
441+
// persistentStoreBuilder.CacheTime(TimeSpan.FromSeconds(storeConfig.Cache.TTL.Value));
442+
// }
443+
// else if (cacheMode == "infinite")
444+
// {
445+
// persistentStoreBuilder.CacheForever();
446+
// }
447+
448+
// // Determine store mode
449+
// var storeMode = sdkParams.DataSystem.StoreMode == 0
450+
// ? DataSystemConfiguration.DataStoreMode.ReadOnly
451+
// : DataSystemConfiguration.DataStoreMode.ReadWrite;
452+
453+
// dataSystemBuilder.PersistentStore(persistentStoreBuilder, storeMode);
454+
// }
455+
// }
456+
457+
// Configure initializers
458+
if (sdkParams.DataSystem.Initializers != null && sdkParams.DataSystem.Initializers.Length > 0)
459+
{
460+
var initializers = new List<IComponentConfigurer<IDataSource>>();
461+
foreach (var initializer in sdkParams.DataSystem.Initializers)
462+
{
463+
if (initializer.Polling != null)
464+
{
465+
var pollingBuilder = DataSystemComponents.Polling();
466+
if (initializer.Polling.BaseUri != null)
467+
{
468+
endpoints.Polling(initializer.Polling.BaseUri);
469+
}
470+
if (initializer.Polling.PollIntervalMs.HasValue)
471+
{
472+
pollingBuilder.PollInterval(TimeSpan.FromMilliseconds(initializer.Polling.PollIntervalMs.Value));
473+
}
474+
if (!string.IsNullOrEmpty(sdkParams.DataSystem.PayloadFilter))
475+
{
476+
// PayloadFilter is not yet supported in FDv2 builders, so we skip it for now
477+
// TODO: Add PayloadFilter support when available
478+
}
479+
initializers.Add(pollingBuilder);
480+
}
481+
}
482+
if (initializers.Count > 0)
483+
{
484+
dataSystemBuilder.Initializers(initializers.ToArray());
485+
}
486+
}
487+
488+
// Configure synchronizers
489+
if (sdkParams.DataSystem.Synchronizers != null)
490+
{
491+
var synchronizers = new List<IComponentConfigurer<IDataSource>>();
492+
493+
// Primary synchronizer
494+
if (sdkParams.DataSystem.Synchronizers.Primary != null)
495+
{
496+
var primary = CreateSynchronizer(sdkParams.DataSystem.Synchronizers.Primary, endpoints, sdkParams.DataSystem.PayloadFilter);
497+
if (primary != null)
498+
{
499+
synchronizers.Add(primary);
500+
}
501+
}
502+
503+
// Secondary synchronizer (optional)
504+
if (sdkParams.DataSystem.Synchronizers.Secondary != null)
505+
{
506+
var secondary = CreateSynchronizer(sdkParams.DataSystem.Synchronizers.Secondary, endpoints, sdkParams.DataSystem.PayloadFilter);
507+
if (secondary != null)
508+
{
509+
synchronizers.Add(secondary);
510+
}
511+
}
512+
513+
if (synchronizers.Count > 0)
514+
{
515+
dataSystemBuilder.Synchronizers(synchronizers.ToArray());
516+
}
517+
}
518+
519+
builder.DataSystem(dataSystemBuilder);
520+
}
521+
375522
return builder.Build();
376523
}
377524

525+
private static IComponentConfigurer<IDataSource> CreateSynchronizer(
526+
SdkConfigSynchronizerParams synchronizer,
527+
ServiceEndpointsBuilder endpoints,
528+
string payloadFilter)
529+
{
530+
if (synchronizer.Polling != null)
531+
{
532+
var pollingBuilder = DataSystemComponents.Polling();
533+
if (synchronizer.Polling.BaseUri != null)
534+
{
535+
endpoints.Polling(synchronizer.Polling.BaseUri);
536+
}
537+
if (synchronizer.Polling.PollIntervalMs.HasValue)
538+
{
539+
pollingBuilder.PollInterval(TimeSpan.FromMilliseconds(synchronizer.Polling.PollIntervalMs.Value));
540+
}
541+
if (!string.IsNullOrEmpty(payloadFilter))
542+
{
543+
// PayloadFilter is not yet supported in FDv2 builders, so we skip it for now
544+
// TODO: Add PayloadFilter support when available
545+
}
546+
return pollingBuilder;
547+
}
548+
else if (synchronizer.Streaming != null)
549+
{
550+
var streamingBuilder = DataSystemComponents.Streaming();
551+
if (synchronizer.Streaming.BaseUri != null)
552+
{
553+
endpoints.Streaming(synchronizer.Streaming.BaseUri);
554+
}
555+
if (synchronizer.Streaming.InitialRetryDelayMs.HasValue)
556+
{
557+
streamingBuilder.InitialReconnectDelay(TimeSpan.FromMilliseconds(synchronizer.Streaming.InitialRetryDelayMs.Value));
558+
}
559+
if (!string.IsNullOrEmpty(payloadFilter))
560+
{
561+
// PayloadFilter is not yet supported in FDv2 builders, so we skip it for now
562+
// TODO: Add PayloadFilter support when available
563+
}
564+
return streamingBuilder;
565+
}
566+
return null;
567+
}
568+
378569
private MigrationVariationResponse DoMigrationVariation(MigrationVariationParams migrationVariation)
379570
{
380571
var defaultStage = MigrationStageExtensions.FromDataModelString(migrationVariation.DefaultStage);

pkgs/sdk/server/contract-tests/test-supressions-fdv2.txt

Whitespace-only changes.

pkgs/sdk/server/src/Properties/AssemblyInfo.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
// tests must be run against the Debug configuration of this assembly)
1616
[assembly: InternalsVisibleTo("LaunchDarkly.ServerSdk.Tests")]
1717

18+
// Allow contract tests to see internal classes
19+
[assembly: InternalsVisibleTo("ContractTestService")]
20+
1821
// Allow mock/proxy objects in unit tests to access internal classes
1922
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
2023
#endif

0 commit comments

Comments
 (0)