diff --git a/.github/workflows/test-sdk-packages.yml b/.github/workflows/test-sdk-packages.yml index 5fad22fd..8f849ffa 100644 --- a/.github/workflows/test-sdk-packages.yml +++ b/.github/workflows/test-sdk-packages.yml @@ -8,37 +8,52 @@ on: workflow_dispatch: jobs: + test-dart-sdk: + strategy: + fail-fast: false + matrix: + platform: ["linux"] + uses: ./.github/workflows/test-server-sdk.yml + with: + platform: ${{ matrix.platform }} + sdkName: "eppo/dart-sdk" + sdkRelayDir: "dart-sdk-relay" + setupAction: "dart-lang/setup-dart@v1" + setupWith: '{"sdk": "stable"}' + secrets: inherit + test-php-sdk: strategy: fail-fast: false matrix: - platform: ['linux'] + platform: ["linux"] uses: ./.github/workflows/test-server-sdk.yml with: platform: ${{ matrix.platform }} - sdkName: 'eppo/php-sdk' - sdkRelayDir: 'php-sdk-relay' + sdkName: "eppo/php-sdk" + sdkRelayDir: "php-sdk-relay" secrets: inherit test-python-sdk: strategy: fail-fast: false matrix: - platform: ['linux'] + platform: ["linux"] uses: ./.github/workflows/test-server-sdk.yml with: platform: ${{ matrix.platform }} - sdkName: 'eppo/python-sdk' - sdkRelayDir: 'python-sdk-relay' + sdkName: "eppo/python-sdk" + sdkRelayDir: "python-sdk-relay" secrets: inherit test-node-sdk: strategy: fail-fast: false - matrix: ['linux'] + matrix: + platform: ["linux"] uses: ./.github/workflows/test-server-sdk.yml with: platform: ${{ matrix.platform }} - sdkName: 'eppo/node-server-sdk' - sdkRelayDir: 'node-sdk-relay' + sdkName: "eppo/node-server-sdk" + sdkRelayDir: "node-sdk-relay" secrets: inherit diff --git a/.github/workflows/test-server-sdk.yml b/.github/workflows/test-server-sdk.yml index f65cec73..ea2e17e9 100644 --- a/.github/workflows/test-server-sdk.yml +++ b/.github/workflows/test-server-sdk.yml @@ -4,21 +4,28 @@ on: workflow_call: inputs: platform: - description: 'Platforms to test the SDK Relay on; linux, macos, windows' + description: "Platforms to test the SDK Relay on; linux, macos, windows" type: string required: true sdkName: - description: 'Name of the SDK' + description: "Name of the SDK" type: string required: true sdkRelayDir: - description: 'Directory of the SDK Relay server code' + description: "Directory of the SDK Relay server code" type: string required: false os: - description: 'Specific runner OS to use' + description: "Specific runner OS to use" type: string - + setupAction: + description: "Optional GitHub action to run for setup (e.g., dart-lang/setup-dart@v1)" + type: string + required: false + setupWith: + description: "Optional JSON string of parameters to pass to the setup action" + type: string + required: false jobs: test-packaged-server-sdks: @@ -37,66 +44,73 @@ jobs: GAR_LOCATION: ${{ vars.SDK_TESTING_REGION }}-docker.pkg.dev/${{ vars.SDK_TESTING_PROJECT_ID }}/sdk-testing steps: - - name: Test information header - shell: bash - run: echo "Running Test Cluster for ${SDK_NAME}" - - - name: Set some variables - id: vars - run: | - echo "::set-output name=date::$(date +'%Y-%m-%d')" - echo "SAFE_SDK_NAME=$(echo ${SDK_NAME} | sed 's/\//_/g')" >> $GITHUB_ENV - - - - name: "Checkout" - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - - # Set up docker (macos runners) - - id: setup-docker - if: ${{ inputs.platform == 'macos' }} - name: Setup Docker - uses: douglascamata/setup-docker-macos-action@v1-alpha - - # Set up gCloud - - id: "auth" - uses: "google-github-actions/auth@v1" - with: - credentials_json: "${{ secrets.SERVICE_ACCOUNT_KEY }}" - - - name: "Set up Cloud SDK" - uses: "google-github-actions/setup-gcloud@v1" - - # Allow docker access to the GAR - - name: "Docker auth" - run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet - - # Pull test runner and testing api images for GCP Artifact Registry (GAR) and - # retag them locally as expected by the runner script. - - name: Pull Test Runner image - run: | - docker pull ${{ env.GAR_LOCATION }}/sdk-test-runner:latest - docker tag ${{ env.GAR_LOCATION }}/sdk-test-runner:latest Eppo-exp/sdk-test-runner:latest - docker pull ${{ env.GAR_LOCATION }}/testing-api:latest - docker tag ${{ env.GAR_LOCATION }}/testing-api:latest Eppo-exp/testing-api:latest - - - name: Run tests - run: | - pushd package-testing/sdk-test-runner - ./test-sdk.sh server ${SDK_NAME} - popd - - - name: Upload Logs - if: success() || failure() # always run even if the previous steps fail - - uses: actions/upload-artifact@v4 - with: - name: ${{ steps.date.outputs.date }}-${{ env.SAFE_SDK_NAME }}-${{ inputs.platform }}-test-logs - path: package-testing/sdk-test-runner/logs/ - - - name: Publish Test Report - uses: mikepenz/action-junit-report@v5 - if: success() || failure() # always run even if the previous steps fail - with: - report_paths: 'package-testing/sdk-test-runner/logs/results.xml' + - name: Test information header + shell: bash + run: echo "Running Test Cluster for ${SDK_NAME}" + + - name: Set some variables + id: vars + run: | + echo "::set-output name=date::$(date +'%Y-%m-%d')" + echo "SAFE_SDK_NAME=$(echo ${SDK_NAME} | sed 's/\//_/g')" >> $GITHUB_ENV + + - name: "Checkout" + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + + # Optional setup action + - name: "Run setup action" + if: ${{ inputs.setupAction != '' }} + uses: jenseng/dynamic-uses@v1 + with: + uses: ${{ inputs.setupAction }} + with: ${{ inputs.setupWith || '{}' }} + + # Set up docker (macos runners) + - id: setup-docker + if: ${{ inputs.platform == 'macos' }} + name: Setup Docker + uses: douglascamata/setup-docker-macos-action@v1-alpha + + # Set up gCloud + - id: "auth" + uses: "google-github-actions/auth@v1" + with: + credentials_json: "${{ secrets.SERVICE_ACCOUNT_KEY }}" + + - name: "Set up Cloud SDK" + uses: "google-github-actions/setup-gcloud@v1" + + # Allow docker access to the GAR + - name: "Docker auth" + run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet + + # Pull test runner and testing api images for GCP Artifact Registry (GAR) and + # retag them locally as expected by the runner script. + - name: Pull Test Runner image + run: | + docker pull ${{ env.GAR_LOCATION }}/sdk-test-runner:latest + docker tag ${{ env.GAR_LOCATION }}/sdk-test-runner:latest Eppo-exp/sdk-test-runner:latest + docker pull ${{ env.GAR_LOCATION }}/testing-api:latest + docker tag ${{ env.GAR_LOCATION }}/testing-api:latest Eppo-exp/testing-api:latest + + - name: Run tests + run: | + pushd package-testing/sdk-test-runner + ./test-sdk.sh server ${SDK_NAME} + popd + + - name: Upload Logs + if: success() || failure() # always run even if the previous steps fail + + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.date.outputs.date }}-${{ env.SAFE_SDK_NAME }}-${{ inputs.platform }}-test-logs + path: package-testing/sdk-test-runner/logs/ + + - name: Publish Test Report + uses: mikepenz/action-junit-report@v5 + if: success() || failure() # always run even if the previous steps fail + with: + report_paths: "package-testing/sdk-test-runner/logs/results.xml" diff --git a/package-testing/dart-sdk-relay/.env.EXAMPLE b/package-testing/dart-sdk-relay/.env.EXAMPLE new file mode 100644 index 00000000..e2085472 --- /dev/null +++ b/package-testing/dart-sdk-relay/.env.EXAMPLE @@ -0,0 +1,4 @@ +SDK_RELAY_PORT=4000 +EPPO_BASE_URL=http://localhost:5000/api +EPPO_API_KEY=NOKEYSPECIFIED +SDK_REF=main \ No newline at end of file diff --git a/package-testing/dart-sdk-relay/.gitignore b/package-testing/dart-sdk-relay/.gitignore new file mode 100644 index 00000000..97c58527 --- /dev/null +++ b/package-testing/dart-sdk-relay/.gitignore @@ -0,0 +1,6 @@ +.env +.dart_tool/ +.packages +pubspec.lock +vendor/ +lib/native/ diff --git a/package-testing/dart-sdk-relay/bin/server.dart b/package-testing/dart-sdk-relay/bin/server.dart new file mode 100644 index 00000000..8b28a0a1 --- /dev/null +++ b/package-testing/dart-sdk-relay/bin/server.dart @@ -0,0 +1,176 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:ffi'; + +import 'package:shelf/shelf.dart'; +import 'package:shelf/shelf_io.dart'; +import 'package:shelf_router/shelf_router.dart'; +import 'package:dotenv/dotenv.dart'; +import 'package:logging/logging.dart'; +import 'package:eppo_sdk/eppo_sdk.dart'; +import 'package:path/path.dart' as path; + +import 'package:dart_sdk_relay/config.dart'; +import 'package:dart_sdk_relay/assignment_handler.dart'; +import 'package:dart_sdk_relay/bandit_handler.dart'; +import 'package:dart_sdk_relay/relay_logger.dart'; + +// Configure a logger +final _logger = Logger('dart_sdk_relay'); + +void main(List args) async { + // Load environment variables + var env = DotEnv(includePlatformEnvironment: true)..load(); + + // Configure logging + Logger.root.level = Level.INFO; + Logger.root.onRecord.listen((record) { + stdout.writeln('${record.level.name}: ${record.time}: ${record.message}'); + }); + + // Initialize configuration + final config = Config(); + _logger.info('Starting server with API server: ${config.apiServer}'); + + // Set up the native library path + try { + // Get the directory where the executable is running + final scriptDir = path.dirname(Platform.script.toFilePath()); + final workingDir = Directory.current.path; + + // Try to find the native library in various locations + final possiblePaths = [ + path.join(workingDir, 'lib', 'native', 'libeppo_client.dylib'), + path.join(scriptDir, '..', 'lib', 'native', 'libeppo_client.dylib'), + path.join(workingDir, 'native', 'libeppo_client.dylib'), + ]; + + String? libraryPath; + for (final p in possiblePaths) { + if (File(p).existsSync()) { + libraryPath = p; + _logger.info('Found native library at: $libraryPath'); + break; + } + } + + if (libraryPath != null) { + // Set the environment variable for the native library + _logger.info('Setting RUST_LIBRARY_PATH to: $libraryPath'); + Platform.environment['RUST_LIBRARY_PATH'] = libraryPath; + } else { + _logger.severe( + 'Could not find native library in any of the expected locations'); + } + } catch (e) { + _logger.severe('Error setting up native library path: $e'); + } + + // Initialize the Eppo client + final relayLogger = RelayLogger(); + EppoClient eppoClient = + await initEppoClient(config.apiKey, config.apiServer, relayLogger); + + // Create handlers + final assignmentHandler = AssignmentHandler(eppoClient, relayLogger); + final banditHandler = BanditHandler(eppoClient, relayLogger); + + // Create a router + final app = Router(); + + // Define routes + app.get('/', (Request request) { + return Response.ok('hello, world'); + }); + + app.get('/sdk/details', (Request request) { + final details = { + 'sdkName': 'eppo-dart-sdk', + 'sdkVersion': + '1.0.0', // This should be dynamically determined if possible + 'supportsBandits': true, + 'supportsDynamicTyping': true, + }; + return Response.ok(jsonEncode(details), + headers: {'Content-Type': 'application/json'}); + }); + + app.post('/sdk/reset', (Request request) async { + // Reset the Eppo client (clear caches) + await resetEppoClient(); + return Response.ok('Reset complete'); + }); + + app.post('/flags/v1/assignment', (Request request) async { + final payload = + await request.readAsString().then(jsonDecode) as Map; + final result = await assignmentHandler.getAssignment(payload); + return Response.ok(jsonEncode(result), + headers: {'Content-Type': 'application/json'}); + }); + + app.post('/bandits/v1/action', (Request request) async { + final payload = + await request.readAsString().then(jsonDecode) as Map; + final result = await banditHandler.getBanditAction(payload); + return Response.ok(jsonEncode(result), + headers: {'Content-Type': 'application/json'}); + }); + + // Create a handler from the router + final handler = Pipeline().addMiddleware(logRequests()).addHandler(app); + + // Get host and port from environment + final host = env['SDK_RELAY_HOST'] ?? 'localhost'; + final port = int.parse(env['SDK_RELAY_PORT'] ?? '7001'); + + // Start the server + _logger.info('Starting server on $host:$port'); + final server = await serve(handler, '0.0.0.0', port); + _logger.info('Server listening on ${server.address.host}:${server.port}'); +} + +// Update the initEppoClient function +Future initEppoClient( + String apiKey, String apiServer, RelayLogger logger) async { + _logger.info('Initializing Eppo client with API server: $apiServer'); + + // Create a custom assignment logger that forwards to our RelayLogger + final assignmentLogger = _EppoRelayLogger(logger); + + // Create the Eppo client + final client = EppoClient( + sdkKey: apiKey, + baseUrl: Uri.parse(apiServer), + logger: assignmentLogger, + ); + + // Wait for the client to be ready + await client.whenReady(); + + return client; +} + +// Add this class to bridge between Eppo's logger and our RelayLogger +class _EppoRelayLogger extends AssignmentLogger { + final RelayLogger relayLogger; + + _EppoRelayLogger(this.relayLogger); + + @override + void logAssignment(Map event) { + relayLogger.logAssignment(event); + } + + @override + void logBanditAction(Map event) { + relayLogger.logBanditAction(event); + } +} + +// Update the resetEppoClient function +Future resetEppoClient() async { + // This would need to be implemented based on the Eppo SDK + // If the SDK has a reset method, call it here + _logger.info('Resetting Eppo client'); +} diff --git a/package-testing/dart-sdk-relay/build-and-run.sh b/package-testing/dart-sdk-relay/build-and-run.sh new file mode 100755 index 00000000..90f15afa --- /dev/null +++ b/package-testing/dart-sdk-relay/build-and-run.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# Set default values for vars +: "${SDK_REF:=main}" + +SDK="https://github.com/Eppo-exp/eppo-multiplatform.git" + +if [ -e .env ]; then + source .env +fi + +# Create vendor directory for SDK checkout +mkdir -p vendor + +# Clone or update the SDK repository +if [ -d "vendor/eppo-multiplatform" ]; then + echo "Updating ${SDK}@${SDK_REF}" + cd vendor/eppo-multiplatform + git fetch + git checkout ${SDK_REF} + git pull + cd ../.. +else + echo "Cloning ${SDK}@${SDK_REF}" + git clone -b ${SDK_REF} --depth 1 --single-branch ${SDK} vendor/eppo-multiplatform || ( + echo "Cloning repo failed" + exit 1 + ) +fi + +# Create .cargo directory and config.toml for patching crates +mkdir -p vendor/eppo-multiplatform/dart-sdk/rust/.cargo +cat > vendor/eppo-multiplatform/dart-sdk/rust/.cargo/config.toml << EOF +[patch.crates-io] +eppo_core = { path = '../../eppo_core' } +EOF + +# Create native library directory +mkdir -p lib/native + +# Build the native library +cd vendor/eppo-multiplatform/dart-sdk +cargo build --release +cd ../../.. + +# Copy the built library to the native directory +cp vendor/eppo-multiplatform/target/release/libeppo_client.dylib lib/native/ + +export DYLD_LIBRARY_PATH=./lib/native:$DYLD_LIBRARY_PATH + +# Install dependencies +echo "Installing dependencies..." +dart pub get + +echo "Listening on ${SDK_RELAY_HOST}:${SDK_RELAY_PORT}" + +# Run the server +dart --enable-experiment=native-assets run bin/server.dart diff --git a/package-testing/dart-sdk-relay/lib/assignment_handler.dart b/package-testing/dart-sdk-relay/lib/assignment_handler.dart new file mode 100644 index 00000000..f871eefd --- /dev/null +++ b/package-testing/dart-sdk-relay/lib/assignment_handler.dart @@ -0,0 +1,95 @@ +import 'package:logging/logging.dart'; +import 'package:eppo_sdk/eppo_sdk.dart'; +import 'relay_logger.dart'; + +class AssignmentHandler { + final _logger = Logger('AssignmentHandler'); + final EppoClient eppoClient; + final RelayLogger relayLogger; + + // Map of assignment types to method names + static const Map _methods = { + 'INTEGER': 'getIntegerAssignment', + 'STRING': 'getStringAssignment', + 'BOOLEAN': 'getBooleanAssignment', + 'NUMERIC': 'getNumericAssignment', + 'JSON': 'getJSONAssignment', + }; + + AssignmentHandler(this.eppoClient, this.relayLogger); + + Future> getAssignment(Map payload) async { + final variationType = payload['assignmentType'] as String; + final flagKey = payload['flag'] as String; + final defaultValue = payload['defaultValue']; + final subjectKey = payload['subjectKey'] as String; + final subjectAttributes = payload['subjectAttributes'] as Map?; + + _logger.info('Processing assignment for flag: $flagKey, subject: $subjectKey, type: $variationType'); + + try { + // Create a Subject with attributes + final subject = Subject(subjectKey); + + // Add attributes if they exist + if (subjectAttributes != null) { + _addAttributes(subject, subjectAttributes); + } + + // Get assignment based on variation type + dynamic result; + switch (variationType) { + case 'STRING': + result = eppoClient.stringAssignment(flagKey, subject, defaultValue as String? ?? ''); + break; + case 'INTEGER': + result = eppoClient.integerAssignment(flagKey, subject, defaultValue as int? ?? 0); + break; + case 'BOOLEAN': + result = eppoClient.booleanAssignment(flagKey, subject, defaultValue as bool? ?? false); + break; + case 'NUMERIC': + result = eppoClient.numericAssignment(flagKey, subject, defaultValue as double? ?? 0.0); + break; + case 'JSON': + result = eppoClient.jsonAssignment(flagKey, subject, defaultValue as Map? ?? {}); + break; + default: + throw Exception('Invalid variation type $variationType'); + } + + return { + 'subjectKey': subjectKey, + 'result': result, + 'request': payload, + 'assignmentLog': relayLogger.assignmentLogs, + }; + } catch (e) { + _logger.severe('Error getting assignment: $e'); + return { + 'subjectKey': subjectKey, + 'result': defaultValue, + 'error': e.toString(), + 'assignmentLog': relayLogger.assignmentLogs, + }; + } finally { + relayLogger.resetLogs(); + } + } + + // Helper method to add attributes to a subject + void _addAttributes(Subject subject, Map attributes) { + for (final entry in attributes.entries) { + final key = entry.key; + final value = entry.value; + + if (value is String) { + subject.stringAttribute(key, value); + } else if (value is int || value is double) { + subject.numberAttribute(key, value); + } else if (value is bool) { + subject.boolAttribute(key, value); + } + } + } +} \ No newline at end of file diff --git a/package-testing/dart-sdk-relay/lib/bandit_handler.dart b/package-testing/dart-sdk-relay/lib/bandit_handler.dart new file mode 100644 index 00000000..2c3db5d9 --- /dev/null +++ b/package-testing/dart-sdk-relay/lib/bandit_handler.dart @@ -0,0 +1,123 @@ +import 'package:logging/logging.dart'; +import 'package:eppo_sdk/eppo_sdk.dart'; +import 'relay_logger.dart'; + +class BanditHandler { + final _logger = Logger('BanditHandler'); + final EppoClient eppoClient; + final RelayLogger relayLogger; + + BanditHandler(this.eppoClient, this.relayLogger); + + Future> getBanditAction(Map payload) async { + _logger.info('Processing Bandit action'); + _logger.fine('Payload: $payload'); + + final flagKey = payload['flag'] as String; + final defaultValue = payload['defaultValue'] as String?; + final subjectKey = payload['subjectKey'] as String; + + // Create subject with attributes + final subject = Subject(subjectKey); + + // Add subject attributes if they exist + if (payload['subjectAttributes'] != null) { + final numericAttrs = payload['subjectAttributes']['numericAttributes'] as Map?; + final categoricalAttrs = payload['subjectAttributes']['categoricalAttributes'] as Map?; + + _addNumericAttributes(subject, numericAttrs); + _addCategoricalAttributes(subject, categoricalAttrs); + } + + // Process actions + final actionsList = payload['actions'] as List; + final actionAttributes = {}; + + for (final actionItem in actionsList) { + final action = actionItem as Map; + final actionKey = action['actionKey'] as String; + + final attrs = Attributes(); + + // Add numeric attributes + final numericAttrs = action['numericAttributes'] as Map?; + if (numericAttrs != null) { + for (final entry in numericAttrs.entries) { + attrs.numberAttribute(entry.key, entry.value); + } + } + + // Add categorical attributes + final categoricalAttrs = action['categoricalAttributes'] as Map?; + if (categoricalAttrs != null) { + for (final entry in categoricalAttrs.entries) { + if (entry.value is bool) { + attrs.boolAttribute(entry.key, entry.value); + } else { + attrs.stringAttribute(entry.key, entry.value.toString()); + } + } + } + + actionAttributes[actionKey] = attrs; + } + + _logger.info('Flag: $flagKey, Subject: $subjectKey, Actions: ${actionAttributes.keys.join(', ')}'); + + try { + // Call the real bandit action method + final banditResult = eppoClient.banditAction( + flagKey, + subject, + actionAttributes, + defaultValue ?? '', + ); + + return { + 'subjectKey': subjectKey, + 'result': { + 'variation': banditResult.variation, + 'action': banditResult.action, + }, + 'request': payload, + 'assignmentLog': relayLogger.assignmentLogs, + 'banditLog': relayLogger.banditLogs, + }; + } catch (e) { + _logger.severe('Error getting bandit action: $e'); + return { + 'subjectKey': subjectKey, + 'result': { + 'variation': null, + 'action': defaultValue, + }, + 'error': e.toString(), + 'assignmentLog': relayLogger.assignmentLogs, + 'banditLog': relayLogger.banditLogs, + }; + } finally { + relayLogger.resetLogs(); + } + } + + // Helper methods to add attributes to a subject + void _addNumericAttributes(Subject subject, Map? attributes) { + if (attributes == null) return; + + for (final entry in attributes.entries) { + subject.numberAttribute(entry.key, entry.value); + } + } + + void _addCategoricalAttributes(Subject subject, Map? attributes) { + if (attributes == null) return; + + for (final entry in attributes.entries) { + if (entry.value is bool) { + subject.boolAttribute(entry.key, entry.value); + } else { + subject.stringAttribute(entry.key, entry.value.toString()); + } + } + } +} \ No newline at end of file diff --git a/package-testing/dart-sdk-relay/lib/config.dart b/package-testing/dart-sdk-relay/lib/config.dart new file mode 100644 index 00000000..c7891efd --- /dev/null +++ b/package-testing/dart-sdk-relay/lib/config.dart @@ -0,0 +1,11 @@ +import 'dart:io'; +import 'package:dotenv/dotenv.dart'; + +class Config { + final String apiKey; + final String apiServer; + + Config() : + apiKey = Platform.environment['EPPO_API_KEY'] ?? DotEnv()['EPPO_API_KEY'] ?? 'NOKEYSPECIFIED', + apiServer = Platform.environment['EPPO_BASE_URL'] ?? DotEnv()['EPPO_BASE_URL'] ?? 'http://localhost:5000/api'; +} \ No newline at end of file diff --git a/package-testing/dart-sdk-relay/lib/relay_logger.dart b/package-testing/dart-sdk-relay/lib/relay_logger.dart new file mode 100644 index 00000000..acf16572 --- /dev/null +++ b/package-testing/dart-sdk-relay/lib/relay_logger.dart @@ -0,0 +1,30 @@ +import 'package:logging/logging.dart'; + +// This class will need to be updated based on the actual Eppo Dart SDK implementation +class RelayLogger { + final _logger = Logger('RelayLogger'); + + // Store assignment logs + final List> assignmentLogs = []; + + // Store bandit logs + final List> banditLogs = []; + + // Log an assignment event + void logAssignment(Map assignmentEvent) { + _logger.fine('Logging assignment: $assignmentEvent'); + assignmentLogs.add(assignmentEvent); + } + + // Log a bandit action event + void logBanditAction(Map banditActionEvent) { + _logger.fine('Logging bandit action: $banditActionEvent'); + banditLogs.add(banditActionEvent); + } + + // Reset all logs + void resetLogs() { + assignmentLogs.clear(); + banditLogs.clear(); + } +} \ No newline at end of file diff --git a/package-testing/dart-sdk-relay/pubspec.yaml b/package-testing/dart-sdk-relay/pubspec.yaml new file mode 100644 index 00000000..628f2e87 --- /dev/null +++ b/package-testing/dart-sdk-relay/pubspec.yaml @@ -0,0 +1,20 @@ +name: dart_sdk_relay +description: Eppo Dart SDK Relay Server +version: 1.0.0 + +environment: + sdk: '>=2.19.0 <3.0.0' + +dependencies: + shelf: ^1.4.0 + shelf_router: ^1.1.3 + http: ^1.3.0 + dotenv: ^4.1.0 + path: ^1.8.3 + logging: ^1.1.1 + args: ^2.4.0 + eppo_sdk: + path: ./vendor/eppo-multiplatform/dart-sdk + +dev_dependencies: + lints: ^5.1.1 diff --git a/package-testing/dart-sdk-relay/release.sh b/package-testing/dart-sdk-relay/release.sh new file mode 100755 index 00000000..475b37b7 --- /dev/null +++ b/package-testing/dart-sdk-relay/release.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +docker build . -t Eppo-exp/dart-sdk-relay:latest +docker tag Eppo-exp/dart-sdk-relay:latest Eppo-exp/dart-sdk-relay:$1