You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by pi...@apache.org on 2025/07/08 19:59:04 UTC

(iggy) branch master updated: feat(csharp): add BDD tests (#1980)

This is an automated email from the ASF dual-hosted git repository.

piotr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new c0a871b1 feat(csharp): add BDD tests (#1980)
c0a871b1 is described below

commit c0a871b1bb8e998784b1dd601366dcfd49014d77
Author: Łukasz Zborek <lu...@gmail.com>
AuthorDate: Tue Jul 8 21:59:00 2025 +0200

    feat(csharp): add BDD tests (#1980)
    
    Added BDD tests for csharp SDK
---
 .github/changed-files-config.json                  |   3 +-
 .github/workflows/ci-check-csharp-sdk.yml          |   4 +-
 .github/workflows/ci-test-bdd.yml                  |   2 +-
 bdd/README.md                                      |   7 +
 .../ci-test-bdd.yml => bdd/csharp/Dockerfile       |  34 +-
 bdd/docker-compose.yml                             |  10 +
 foreign/csharp/.dockerignore                       | 400 +++++++++++++++++++++
 foreign/csharp/DEPENDENCIES.md                     |   1 +
 foreign/csharp/Directory.Packages.props            |  43 +--
 .../Iggy_SDK.Tests.BDD/Context/TestContext.cs      |  32 ++
 .../csharp/Iggy_SDK.Tests.BDD/Context/TestHooks.cs |  55 +++
 .../Iggy_SDK.Tests.BDD/Iggy_SDK.Tests.BDD.csproj   |  44 +++
 foreign/csharp/Iggy_SDK.Tests.BDD/README.md        |  21 ++
 .../BasicMessagingOperationsSteps.cs               | 224 ++++++++++++
 foreign/csharp/Iggy_SDK.sln                        |   6 +
 foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs    |   3 +-
 foreign/csharp/Iggy_SDK/Messages/Message.cs        |  13 +
 .../Iggy_SDK_Tests/MapperTests/BinaryMapper.cs     |   1 +
 scripts/run-bdd-tests.sh                           |  12 +-
 19 files changed, 860 insertions(+), 55 deletions(-)

diff --git a/.github/changed-files-config.json b/.github/changed-files-config.json
index 3b429b74..a96ea4e2 100644
--- a/.github/changed-files-config.json
+++ b/.github/changed-files-config.json
@@ -73,6 +73,7 @@
     "bdd/docker-compose.yml",
     "scripts/run-bdd-tests.sh",
     ".github/workflows/ci-test-bdd.yml",
-    "core/.*\\.rs"
+    "core/.*\\.rs",
+    "foreign/csharp/Iggy_SDK.Tests.BDD/.*\\.cs"
   ]
 }
diff --git a/.github/workflows/ci-check-csharp-sdk.yml b/.github/workflows/ci-check-csharp-sdk.yml
index 5944b7a3..5cb85711 100644
--- a/.github/workflows/ci-check-csharp-sdk.yml
+++ b/.github/workflows/ci-check-csharp-sdk.yml
@@ -58,4 +58,6 @@ jobs:
         run: dotnet build --no-restore
       - name: Test
         working-directory: foreign/csharp
-        run: dotnet test --no-build --verbosity normal
+        run: |
+          dotnet test Iggy_SDK_Tests --no-build --verbosity normal
+          dotnet test Iggy_SDK.Tests.Integration --no-build --verbosity normal
diff --git a/.github/workflows/ci-test-bdd.yml b/.github/workflows/ci-test-bdd.yml
index 036f69c3..2f024d04 100644
--- a/.github/workflows/ci-test-bdd.yml
+++ b/.github/workflows/ci-test-bdd.yml
@@ -26,7 +26,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        sdk: [rust, python, go, node]
+        sdk: [rust, python, go, node, csharp]
     steps:
       - name: Checkout code
         uses: actions/checkout@v4
diff --git a/bdd/README.md b/bdd/README.md
index c96156e1..4e065c53 100644
--- a/bdd/README.md
+++ b/bdd/README.md
@@ -18,6 +18,8 @@ bdd/
 │   └── requirements.txt
 ├── node/                       # Node SDK BDD implementation
 │   └── Dockerfile              # Node BDD test container
+├── csharp/                     # csharp SDK BDD implementation
+│   └── Dockerfile              # csharp BDD test container
 ├── docker-compose.yml          # Orchestrates server + SDK containers
 ├── Dockerfile                  # Debug build of Iggy server
 └── README.md
@@ -36,6 +38,7 @@ bdd/
 ../scripts/run-bdd-tests.sh python
 ../scripts/run-bdd-tests.sh go
 ../scripts/run-bdd-tests.sh node
+../scripts/run-bdd-tests.sh csharp
 
 # Clean up Docker resources
 ../scripts/run-bdd-tests.sh clean
@@ -82,6 +85,10 @@ The Python implementation is in `bdd/python/tests/` and needs to be updated as t
 
 The node.js BDD test are run by cucumber-js, bdd test code is located at [foreign/node/src/bdd](../foreign/node/src/bdd/)
 
+### For csharp SDK
+
+The csharp implementation is located at [foreign/csharp/Iggy_SDK.Tests.BDD](../foreign/csharp/Iggy_SDK.Tests.BDD)
+
 ### Adding New Scenarios
 
 Add new `.feature` files to the `bdd/scenarios/` directory and implement the corresponding step definitions in each SDK's test directory.
diff --git a/.github/workflows/ci-test-bdd.yml b/bdd/csharp/Dockerfile
similarity index 51%
copy from .github/workflows/ci-test-bdd.yml
copy to bdd/csharp/Dockerfile
index 036f69c3..8b771805 100644
--- a/.github/workflows/ci-test-bdd.yml
+++ b/bdd/csharp/Dockerfile
@@ -15,34 +15,12 @@
 # specific language governing permissions and limitations
 # under the License.
 
-name: Cross-SDK BDD Tests
+FROM mcr.microsoft.com/dotnet/sdk:8.0
 
-on:
-  workflow_call:
+COPY ./foreign/csharp ./foreign/csharp
+COPY ./bdd/scenarios ./bdd/scenarios
 
-jobs:
-  bdd-tests:
-    name: BDD tests (${{ matrix.sdk }})
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        sdk: [rust, python, go, node]
-    steps:
-      - name: Checkout code
-        uses: actions/checkout@v4
-      
-      - name: Cache Rust dependencies
-        uses: Swatinem/rust-cache@v2
-        with:
-          workspaces: |
-            . -> target
-            foreign/python -> target
-          cache-on-failure: true
-          key: bdd-${{ matrix.sdk }}
+WORKDIR /foreign/csharp/Iggy_SDK.Tests.BDD
+RUN dotnet build -c Release 
 
-      - name: Run BDD tests for ${{ matrix.sdk }}
-        run: ./scripts/run-bdd-tests.sh ${{ matrix.sdk }}
-
-      - name: Clean up Docker resources
-        if: always()
-        run: ./scripts/run-bdd-tests.sh clean
+CMD ["dotnet", "test"]
diff --git a/bdd/docker-compose.yml b/bdd/docker-compose.yml
index a52f1e19..1fc1b488 100644
--- a/bdd/docker-compose.yml
+++ b/bdd/docker-compose.yml
@@ -84,6 +84,16 @@ services:
       - IGGY_TCP_ADDRESS=iggy-server:8090
     command: ["npm", "run", "test:bdd"]
 
+  csharp-bdd:
+    build:
+      context: ..
+      dockerfile: bdd/csharp/Dockerfile
+    depends_on:
+      iggy-server:
+        condition: service_healthy
+    environment:
+      - IGGY_TCP_ADDRESS=iggy-server:8090
+    command: ["dotnet", "test"]
     
 volumes:
   iggy_data:
diff --git a/foreign/csharp/.dockerignore b/foreign/csharp/.dockerignore
new file mode 100644
index 00000000..931afb93
--- /dev/null
+++ b/foreign/csharp/.dockerignore
@@ -0,0 +1,400 @@
+### Csharp template
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+
diff --git a/foreign/csharp/DEPENDENCIES.md b/foreign/csharp/DEPENDENCIES.md
index c311316d..6419947d 100644
--- a/foreign/csharp/DEPENDENCIES.md
+++ b/foreign/csharp/DEPENDENCIES.md
@@ -8,6 +8,7 @@ Microsoft.NET.Test.Sdk: "17.14.1", "MIT",
 Microsoft.Testing.Extensions.CodeCoverage: "17.14.2", "MIT",
 Microsoft.Testing.Extensions.TrxReport: "1.7.3", "MIT",
 Moq: "4.20.72", "BSD-3-Clause",
+Reqnroll.xUnit: "2.4.1", "BSD-3-Clause",
 Shouldly: "4.3.0", "BSD-3-Clause",
 System.IO.Hashing: "8.0.0", "MIT",
 Testcontainers: "4.6.0", "MIT",
diff --git a/foreign/csharp/Directory.Packages.props b/foreign/csharp/Directory.Packages.props
index 02c84e79..941c89d2 100644
--- a/foreign/csharp/Directory.Packages.props
+++ b/foreign/csharp/Directory.Packages.props
@@ -1,22 +1,23 @@
 <Project>
-    <PropertyGroup>
-        <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
-        <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
-    </PropertyGroup>
-    <ItemGroup>
-        <PackageVersion Include="coverlet.collector" Version="6.0.4" />
-        <PackageVersion Include="FluentAssertions" Version="6.12.0" />
-        <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1" />
-        <PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
-        <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
-        <PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.14.2" />
-        <PackageVersion Include="Microsoft.Testing.Extensions.TrxReport" Version="1.7.3" />
-        <PackageVersion Include="Moq" Version="4.20.72" />
-        <PackageVersion Include="Shouldly" Version="4.3.0" />
-        <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
-        <PackageVersion Include="Testcontainers" Version="4.6.0" />
-        <PackageVersion Include="TUnit" Version="0.25.21" />
-        <PackageVersion Include="xunit" Version="2.9.3" />
-        <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.1" />
-    </ItemGroup>
-</Project>
+  <PropertyGroup>
+    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
+    <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageVersion Include="coverlet.collector" Version="6.0.4" />
+    <PackageVersion Include="FluentAssertions" Version="6.12.0" />
+    <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.1" />
+    <PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
+    <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
+    <PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.14.2" />
+    <PackageVersion Include="Microsoft.Testing.Extensions.TrxReport" Version="1.7.3" />
+    <PackageVersion Include="Moq" Version="4.20.72" />
+    <PackageVersion Include="Reqnroll.xUnit" Version="2.4.1" />
+    <PackageVersion Include="Shouldly" Version="4.3.0" />
+    <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
+    <PackageVersion Include="Testcontainers" Version="4.6.0" />
+    <PackageVersion Include="TUnit" Version="0.25.21" />
+    <PackageVersion Include="xunit" Version="2.9.3" />
+    <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.1" />
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs b/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs
new file mode 100644
index 00000000..aa7dc5dc
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs
@@ -0,0 +1,32 @@
+// // Licensed to the Apache Software Foundation (ASF) under one
+// // or more contributor license agreements.  See the NOTICE file
+// // distributed with this work for additional information
+// // regarding copyright ownership.  The ASF licenses this file
+// // to you under the Apache License, Version 2.0 (the
+// // "License"); you may not use this file except in compliance
+// // with the License.  You may obtain a copy of the License at
+// //
+// //   http://www.apache.org/licenses/LICENSE-2.0
+// //
+// // Unless required by applicable law or agreed to in writing,
+// // software distributed under the License is distributed on an
+// // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// // KIND, either express or implied.  See the License for the
+// // specific language governing permissions and limitations
+// // under the License.
+
+using Apache.Iggy.Contracts.Http;
+using Apache.Iggy.IggyClient;
+using Apache.Iggy.Messages;
+
+namespace Apache.Iggy.Tests.BDD.Context;
+
+public class TestContext
+{
+    public IIggyClient IggyClient { get; set; } = null!;
+    public string TcpUrl { get; set; }  = string.Empty;
+    public StreamResponse? CreatedStream { get; set; }
+    public TopicResponse? CreatedTopic { get; set; }
+    public List<MessageResponse> PolledMessages { get; set; } = new();
+    public Message? LastSendMessage { get; set; }
+}
\ No newline at end of file
diff --git a/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestHooks.cs b/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestHooks.cs
new file mode 100644
index 00000000..954dbe62
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestHooks.cs
@@ -0,0 +1,55 @@
+// // Licensed to the Apache Software Foundation (ASF) under one
+// // or more contributor license agreements.  See the NOTICE file
+// // distributed with this work for additional information
+// // regarding copyright ownership.  The ASF licenses this file
+// // to you under the Apache License, Version 2.0 (the
+// // "License"); you may not use this file except in compliance
+// // with the License.  You may obtain a copy of the License at
+// //
+// //   http://www.apache.org/licenses/LICENSE-2.0
+// //
+// // Unless required by applicable law or agreed to in writing,
+// // software distributed under the License is distributed on an
+// // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// // KIND, either express or implied.  See the License for the
+// // specific language governing permissions and limitations
+// // under the License.
+
+using Reqnroll;
+
+namespace Apache.Iggy.Tests.BDD.Context;
+
+[Binding]
+public class TestHooks
+{
+    private readonly TestContext _context;
+
+    public TestHooks(TestContext context)
+    {
+        _context = context;
+    }
+
+    [BeforeScenario]
+    public void BeforeScenario()
+    {
+        _context.TcpUrl = Environment.GetEnvironmentVariable("IGGY_TCP_ADDRESS") ?? "127.0.0.1:8090";
+    }
+
+    [AfterScenario]
+    public void AfterScenario()
+    {
+        //
+    }
+
+    [BeforeFeature]
+    public static void BeforeFeature()
+    {
+        //
+    }
+
+    [AfterFeature]
+    public static void AfterFeature()
+    {
+        //
+    }
+}
\ No newline at end of file
diff --git a/foreign/csharp/Iggy_SDK.Tests.BDD/Iggy_SDK.Tests.BDD.csproj b/foreign/csharp/Iggy_SDK.Tests.BDD/Iggy_SDK.Tests.BDD.csproj
new file mode 100644
index 00000000..09dbafd8
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK.Tests.BDD/Iggy_SDK.Tests.BDD.csproj
@@ -0,0 +1,44 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <TargetFramework>net8.0</TargetFramework>
+        <ImplicitUsings>enable</ImplicitUsings>
+        <Nullable>enable</Nullable>
+        <IsPackable>false</IsPackable>
+        <AssemblyName>Apache.Iggy.Tests.BDD</AssemblyName>
+        <RootNamespace>Apache.Iggy.Tests.BDD</RootNamespace>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <Description>Behavior-Driven Development (BDD) tests in csharp SDK for Apache Iggy</Description>
+        <Authors>Apache Iggy</Authors>
+        <RepositoryUrl>https://github.com/apache/iggy</RepositoryUrl>
+        <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
+        <PackageReadmeFile>README.md</PackageReadmeFile>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="coverlet.collector" />
+        <PackageReference Include="Microsoft.NET.Test.Sdk" />
+        <PackageReference Include="Reqnroll.xUnit" />
+        <PackageReference Include="Shouldly" />
+        <PackageReference Include="xunit" />
+        <PackageReference Include="xunit.runner.visualstudio" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <Using Include="Xunit" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Content Include="..\..\..\bdd\scenarios\basic_messaging.feature">
+        <Link>Features\basic_messaging.feature</Link>
+        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      </Content>
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\Iggy_SDK\Iggy_SDK.csproj" />
+    </ItemGroup>
+
+</Project>
diff --git a/foreign/csharp/Iggy_SDK.Tests.BDD/README.md b/foreign/csharp/Iggy_SDK.Tests.BDD/README.md
new file mode 100644
index 00000000..a92723b2
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK.Tests.BDD/README.md
@@ -0,0 +1,21 @@
+# csharp bdd test
+
+Scenario are located at [/bdd/scenarios](../../../bdd/scenarios)
+
+## env var
+
+use env var `IGGY_TCP_ADDRESS="host:port"` to set expected server address for bdd test suite.
+
+## Run via docker
+
+see [/bdd/README.md](../../../bdd/README.md)
+
+## Run locally
+
+note: bdd test expect an iggy-server at tcp://127.0.0.1:8090
+
+from [/foreign/csharp/Iggy_SDK.Tests.BDD](.) run
+
+```bash
+dotnet test
+```
diff --git a/foreign/csharp/Iggy_SDK.Tests.BDD/StepDefinitions/BasicMessagingOperationsSteps.cs b/foreign/csharp/Iggy_SDK.Tests.BDD/StepDefinitions/BasicMessagingOperationsSteps.cs
new file mode 100644
index 00000000..f0d2ba5c
--- /dev/null
+++ b/foreign/csharp/Iggy_SDK.Tests.BDD/StepDefinitions/BasicMessagingOperationsSteps.cs
@@ -0,0 +1,224 @@
+// // Licensed to the Apache Software Foundation (ASF) under one
+// // or more contributor license agreements.  See the NOTICE file
+// // distributed with this work for additional information
+// // regarding copyright ownership.  The ASF licenses this file
+// // to you under the Apache License, Version 2.0 (the
+// // "License"); you may not use this file except in compliance
+// // with the License.  You may obtain a copy of the License at
+// //
+// //   http://www.apache.org/licenses/LICENSE-2.0
+// //
+// // Unless required by applicable law or agreed to in writing,
+// // software distributed under the License is distributed on an
+// // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// // KIND, either express or implied.  See the License for the
+// // specific language governing permissions and limitations
+// // under the License.
+
+using System.Text;
+using Apache.Iggy.Contracts.Http;
+using Apache.Iggy.Contracts.Http.Auth;
+using Apache.Iggy.Enums;
+using Apache.Iggy.Factory;
+using Apache.Iggy.Kinds;
+using Apache.Iggy.Messages;
+using Apache.Iggy.Tests.BDD.Context;
+using Microsoft.Extensions.Logging.Abstractions;
+using Reqnroll;
+using Shouldly;
+using Partitioning = Apache.Iggy.Kinds.Partitioning;
+
+namespace Apache.Iggy.Tests.BDD.StepDefinitions;
+
+[Binding]
+public class BasicMessagingOperationsSteps
+{
+    private readonly TestContext _context;
+
+    public BasicMessagingOperationsSteps(TestContext context)
+    {
+        _context = context;
+    }
+
+    [Given(@"I have a running Iggy server")]
+    public async Task GivenIHaveARunningIggyServer()
+    {
+        _context.IggyClient = MessageStreamFactory.CreateMessageStream(configurator =>
+        {
+            configurator.BaseAdress = _context.TcpUrl;
+            configurator.Protocol = Protocol.Tcp;
+            configurator.MessageBatchingSettings = settings =>
+            {
+                settings.Enabled = false;
+            };
+        }, NullLoggerFactory.Instance);
+
+        await _context.IggyClient.PingAsync();
+    }
+
+    [Given(@"I am authenticated as the root user")]
+    public async Task GivenIAmAuthenticatedAsTheRootUser()
+    {
+        var loginResult = await _context.IggyClient.LoginUser(new LoginUserRequest()
+        {
+            Username = "iggy",
+            Password = "iggy"
+        });
+
+        loginResult.ShouldNotBeNull();
+        loginResult.UserId.ShouldBe(1);
+    }
+
+    [Given(@"I have no streams in the system")]
+    public async Task GivenIHaveNoStreamsInTheSystem()
+    {
+        var streams = await _context.IggyClient.GetStreamsAsync();
+        
+        streams.ShouldNotBeNull();
+        streams.Count.ShouldBe(0);
+    }
+
+    [When(@"I create a stream with ID (\d+) and name (.*)")]
+    public async Task WhenICreateAStreamWithIdAndName(int streamId, string streamName)
+    {
+        _context.CreatedStream = await _context.IggyClient.CreateStreamAsync(new StreamRequest()
+        {
+            Name = streamName,
+            StreamId = streamId
+        });
+    }
+
+    [Then(@"the stream should be created successfully")]
+    public void ThenTheStreamShouldBeCreatedSuccessfully()
+    {
+        _context.CreatedStream.ShouldNotBeNull();
+        _context.CreatedStream.Id.ShouldNotBe(0);
+        _context.CreatedStream.Name.ShouldNotBeNullOrEmpty();
+    }
+
+    [Then(@"the stream should have ID (\d+) and name (.*)")]
+    public void ThenTheStreamShouldHaveIdAndName(int expectedId, string expectedName)
+    {
+        _context.CreatedStream!.Id.ShouldBe(expectedId);
+        _context.CreatedStream!.Name.ShouldBe(expectedName);
+    }
+
+    [When(@"I create a topic with ID (\d+) and name (.*) in stream (\d+) with (\d+) partitions")]
+    public async Task WhenICreateATopicWithIdAndNameInStreamWithPartitions(int topicId, string topicName, int streamId, int partitions)
+    {
+        _context.CreatedTopic = await _context.IggyClient.CreateTopicAsync(Identifier.Numeric(streamId), new TopicRequest()
+        {
+            TopicId = topicId,
+            Name = topicName,
+            PartitionsCount = partitions
+        });
+    }
+
+    [Then(@"the topic should be created successfully")]
+    public void ThenTheTopicShouldBeCreatedSuccessfully()
+    {
+        _context.CreatedTopic.ShouldNotBeNull();
+        _context.CreatedTopic!.Id.ShouldNotBe(0);
+        _context.CreatedTopic.Name.ShouldNotBeNullOrEmpty();
+    }
+
+    [Then(@"the topic should have ID (\d+) and name (.*)")]
+    public void ThenTheTopicShouldHaveIdAndName(int expectedId, string expectedName)
+    {
+        _context.CreatedTopic!.Id.ShouldBe(expectedId);
+        _context.CreatedTopic!.Name.ShouldBe(expectedName);
+        
+    }
+
+    [Then(@"the topic should have (.*) partitions")]
+    public void ThenTheTopicShouldHavePartitions(int expectedPartitions)
+    {
+        _context.CreatedTopic!.Partitions!.Count().ShouldBe(expectedPartitions);
+        _context.CreatedTopic.PartitionsCount.ShouldBe(expectedPartitions);
+    }
+
+    [When(@"I send (\d+) messages to stream (\d+), topic (\d+), partition (\d+)")]
+    public async Task WhenISendMessagesToStreamTopicPartition(int messageCount, int streamId, int topicId, int partitionId)
+    {
+        var messages = Enumerable.Range(1, messageCount)
+            .Select(i => new Message(1, Encoding.UTF8.GetBytes($"Test message {i}")))
+            .ToList();
+        
+        await _context.IggyClient.SendMessagesAsync(new MessageSendRequest()
+        {
+            StreamId = Identifier.Numeric(streamId),
+            TopicId = Identifier.Numeric(topicId),
+            Partitioning = Partitioning.PartitionId(partitionId),
+            Messages = messages
+        });
+        
+        _context.LastSendMessage = messages.Last();
+    }
+
+    [Then(@"all messages should be sent successfully")]
+    public void ThenAllMessagesShouldBeSentSuccessfully()
+    {
+        _context.LastSendMessage.ShouldNotBeNull();
+    }
+
+    [When(@"I poll messages from stream (\d+), topic (\d+), partition (\d+) starting from offset (\d+)")]
+    public async Task WhenIPollMessagesFromStreamTopicPartitionStartingFromOffset(int streamId, int topicId, int partitionId, ulong startOffset)
+    {
+        var messages = await _context.IggyClient.FetchMessagesAsync(new MessageFetchRequest()
+        {
+            StreamId = Identifier.Numeric(streamId),
+            TopicId = Identifier.Numeric(topicId),
+            PartitionId = partitionId,
+            PollingStrategy = PollingStrategy.Offset(startOffset),
+            Consumer = Consumer.New(1),
+            Count = 100,
+            AutoCommit = false
+        });
+        
+        _context.PolledMessages = messages.Messages.ToList();
+    }
+
+    [Then(@"I should receive (.*) messages")]
+    public void ThenIShouldReceiveMessages(int expectedCount)
+    {
+        _context.PolledMessages.Count.ShouldBe(expectedCount);
+    }
+
+    [Then(@"the messages should have sequential offsets from (\d+) to (\d+)")]
+    public void ThenTheMessagesShouldHaveSequentialOffsetsFromTo(int startOffset, int endOffset)
+    {
+        for (int i = startOffset; i < endOffset; i++)
+        {
+            _context.PolledMessages[i].Header.Offset.ShouldBe((ulong)i);
+            
+        }
+    }
+
+    [Then(@"each message should have the expected payload content")]
+    public void ThenEachMessageShouldHaveTheExpectedPayloadContent()
+    {
+        for (int i = 0; i < _context.PolledMessages.Count; i++)
+        {
+            var message = _context.PolledMessages[i];
+            message.Payload.ShouldNotBeNull();
+            message.Payload.Length.ShouldBeGreaterThan(0);
+            
+            var payloadText = Encoding.UTF8.GetString(message.Payload);
+            payloadText.ShouldBe($"Test message {i + 1}");
+        }
+    }
+
+    [Then(@"the last polled message should match the last sent message")]
+    public void ThenTheLastPolledMessageShouldMatchTheLastSentMessage()
+    {
+        var lastPolled = _context.PolledMessages.LastOrDefault();
+        
+        lastPolled.ShouldNotBeNull();
+        _context.LastSendMessage.ShouldNotBeNull();
+        
+        lastPolled.Header.Id.ShouldBe(_context.LastSendMessage.Value.Header.Id);
+        lastPolled.Payload.ShouldBe(_context.LastSendMessage.Value.Payload);
+    }
+}
+
+// Test context for sharing data between steps
\ No newline at end of file
diff --git a/foreign/csharp/Iggy_SDK.sln b/foreign/csharp/Iggy_SDK.sln
index 536284bf..bd04f22c 100644
--- a/foreign/csharp/Iggy_SDK.sln
+++ b/foreign/csharp/Iggy_SDK.sln
@@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFolder", "SolutionF
 		README.md = README.md
 	EndProjectSection
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Iggy_SDK.Tests.BDD", "Iggy_SDK.Tests.BDD\Iggy_SDK.Tests.BDD.csproj", "{91BFFAE1-1252-4061-BF40-C7E7296F82BC}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -57,5 +59,9 @@ Global
 		{59661E1B-1D5E-4D58-AE7B-10E8ABB9839A}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{59661E1B-1D5E-4D58-AE7B-10E8ABB9839A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{59661E1B-1D5E-4D58-AE7B-10E8ABB9839A}.Release|Any CPU.Build.0 = Release|Any CPU
+		{91BFFAE1-1252-4061-BF40-C7E7296F82BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{91BFFAE1-1252-4061-BF40-C7E7296F82BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{91BFFAE1-1252-4061-BF40-C7E7296F82BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{91BFFAE1-1252-4061-BF40-C7E7296F82BC}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 EndGlobal
diff --git a/foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs b/foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs
index 4dc89310..c7bb4d26 100644
--- a/foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs
+++ b/foreign/csharp/Iggy_SDK/Mappers/BinaryMapper.cs
@@ -26,10 +26,11 @@ using Apache.Iggy.Headers;
 using Apache.Iggy.Messages;
 
 namespace Apache.Iggy.Mappers;
-//TODO - write unit tests for all the users related mappers
+
 internal static class BinaryMapper
 {
     private const int PROPERTIES_SIZE = 56;
+    
     internal static RawPersonalAccessToken MapRawPersonalAccessToken(ReadOnlySpan<byte> payload)
     {
         var tokenLength = payload[0];
diff --git a/foreign/csharp/Iggy_SDK/Messages/Message.cs b/foreign/csharp/Iggy_SDK/Messages/Message.cs
index 0052b21f..42fd332e 100644
--- a/foreign/csharp/Iggy_SDK/Messages/Message.cs
+++ b/foreign/csharp/Iggy_SDK/Messages/Message.cs
@@ -45,6 +45,19 @@ public readonly struct Message
         UserHeaders = userHeaders;
     }
     
+    [System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute]
+    public Message(UInt128 id, byte[] payload, Dictionary<HeaderKey, HeaderValue>? userHeaders = null)
+    {
+        Header = new MessageHeader()
+        {
+            PayloadLength = payload.Length,
+            Id = id,
+            Checksum = CalculateChecksum(payload)
+        };
+        Payload = payload;
+        UserHeaders = userHeaders;
+    }
+    
     public int GetSize()
     {
         //return 56 + Payload.Length + (UserHeaders?.Count ?? 0);
diff --git a/foreign/csharp/Iggy_SDK_Tests/MapperTests/BinaryMapper.cs b/foreign/csharp/Iggy_SDK_Tests/MapperTests/BinaryMapper.cs
index 77801907..6a3f7eee 100644
--- a/foreign/csharp/Iggy_SDK_Tests/MapperTests/BinaryMapper.cs
+++ b/foreign/csharp/Iggy_SDK_Tests/MapperTests/BinaryMapper.cs
@@ -49,6 +49,7 @@ public sealed class BinaryMapper
         Assert.Equal(name, response[0].Name);
         Assert.Equal(assertExpiry, response[0].ExpiryAt);
     }
+    
     [Fact]
     public void MapOffsets_ReturnsValidOffsetResponse()
     {
diff --git a/scripts/run-bdd-tests.sh b/scripts/run-bdd-tests.sh
index 12cdb68d..fe209726 100755
--- a/scripts/run-bdd-tests.sh
+++ b/scripts/run-bdd-tests.sh
@@ -48,17 +48,24 @@ case $SDK in
   docker compose build --no-cache iggy-server node-bdd
   docker compose up --abort-on-container-exit node-bdd
   ;;
+"csharp")
+  echo "🔷 Running csharp BDD tests..."
+  docker compose build --no-cache iggy-server csharp-bdd
+  docker compose up --abort-on-container-exit csharp-bdd
+  ;;
 "all")
   echo "🚀 Running all SDK BDD tests..."
   echo "🦀 Starting with Rust tests..."
-  docker compose build --no-cache iggy-server rust-bdd python-bdd go-bdd
+  #docker compose build --no-cache iggy-server rust-bdd python-bdd go-bdd node-bdd csharp-bdd
   docker compose up --abort-on-container-exit rust-bdd
   echo "🐍 Now running Python tests..."
   docker compose up --abort-on-container-exit python-bdd
   echo "🐹 Now running Go tests..."
   docker compose up --abort-on-container-exit go-bdd
-  echo "🐢🚀 Now unning node BDD tests..."
+  echo "🐢🚀 Now running node BDD tests..."
   docker compose up --abort-on-container-exit node-bdd
+  echo "🔷 Now running csharp BDD tests..."
+  docker compose up --abort-on-container-exit csharp-bdd
   ;;
 "clean")
   echo "🧹 Cleaning up Docker resources..."
@@ -73,6 +80,7 @@ case $SDK in
   echo "   $0 python                  # Run Python tests only"
   echo "   $0 go                      # Run Go tests only"
   echo "   $0 node                    # Run Node.js tests only"
+  echo "   $0 csharp                  # Run csharp tests only"
   echo "   $0 all                     # Run all SDK tests"
   echo "   $0 clean                   # Clean up Docker resources"
   exit 1