Files
rudder-csharp-sdk/tests/Rudder.Core.Tests/RudderClientTests.cs
T

216 lines
7.4 KiB
C#
Raw Normal View History

2026-08-12 14:04:55 +03:00
using RudderSdk.Core;
using RudderSdk.Core.Abstractions;
using RudderSdk.Core.Models.Auth;
using Xunit;
namespace RudderSdk.Core.Tests;
public sealed class RudderClientTests
{
[Fact]
public void Constructor_Composes_All_Runtime_Services()
{
var client = CreateClient();
Assert.NotNull(client.Auth);
Assert.NotNull(client.Player);
Assert.NotNull(client.RemoteConfig);
Assert.NotNull(client.Storage);
Assert.NotNull(client.Stores);
Assert.NotNull(client.Leaderboards);
Assert.NotNull(client.Inventory);
Assert.NotNull(client.BattlePass);
Assert.NotNull(client.Quests);
Assert.NotNull(client.Ugc);
Assert.NotNull(client.Scenario);
Assert.NotNull(client.Realtime);
}
[Fact]
public void Constructor_Requires_BaseUrl_And_ProjectKey()
{
Assert.Throws<ArgumentException>(() => new RudderClient(new RudderClientOptions { ProjectKey = "key" }));
Assert.Throws<ArgumentException>(() => new RudderClient(new RudderClientOptions { BaseUrl = "http://localhost" }));
}
[Fact]
public void Constructor_Uses_HttpClientTransport_By_Default()
{
var client = new RudderClient(new RudderClientOptions
{
BaseUrl = "http://localhost:8082",
ProjectKey = "project-key"
});
Assert.NotNull(client);
}
[Fact]
public async Task Auth_LoginWithDevice_Saves_Tokens_And_Raises_SignedIn()
{
var transport = new FakeTransport();
transport.Responses[typeof(LoginViaDeviceResponse)] = new LoginViaDeviceResponse
{
AccessToken = "access-token",
RefreshToken = "refresh-token"
};
var tokenStore = new FakeTokenStore();
var client = CreateClient(transport: transport, tokenStore: tokenStore);
var states = new List<RudderAuthState>();
client.Auth.AuthStateChanged += states.Add;
await client.Auth.LoginWithDeviceAsync("en", "en", "nickname");
var request = Assert.IsType<LoginViaDeviceRequest>(transport.LastRequest);
Assert.Equal("POST", transport.LastMethod);
Assert.Equal("/sdk/v1/authorization/device", transport.LastPath);
Assert.Equal("nickname", request.Nickname);
Assert.Equal("access-token", tokenStore.AccessToken);
Assert.Equal("refresh-token", tokenStore.RefreshToken);
Assert.Equal(new[] { RudderAuthState.SignedIn }, states);
}
[Fact]
public void Auth_Logout_Clears_Tokens_And_Raises_SignedOut()
{
var tokenStore = new FakeTokenStore { AccessToken = "a", RefreshToken = "r" };
var client = CreateClient(tokenStore: tokenStore);
var states = new List<RudderAuthState>();
client.Auth.AuthStateChanged += states.Add;
client.Auth.Logout();
Assert.Null(tokenStore.AccessToken);
Assert.Null(tokenStore.RefreshToken);
Assert.Equal(new[] { RudderAuthState.SignedOut }, states);
}
[Fact]
public async Task Realtime_Connect_Returns_Session_From_Injected_Transport()
{
var realtimeTransport = new FakeRealtimeTransport();
var tokenStore = new FakeTokenStore { AccessToken = "access-token" };
var client = CreateClient(
tokenStore: tokenStore,
realtimeTransportFactory: new FakeRealtimeTransportFactory(realtimeTransport));
var session = await client.Realtime.ConnectAsync();
Assert.NotNull(session);
Assert.True(session.IsConnected);
Assert.Equal("access-token", session.AccessToken);
Assert.Equal(new Uri("ws://localhost:8090/api/realtime/ws"), realtimeTransport.Uri);
}
private static RudderClient CreateClient(
FakeTransport? transport = null,
FakeTokenStore? tokenStore = null,
IRealtimeTransportFactory? realtimeTransportFactory = null)
{
return new RudderClient(new RudderClientOptions
{
BaseUrl = "http://localhost:8082",
RealtimeUrl = "ws://localhost:8090/api/realtime/ws",
ProjectKey = "project-key",
Transport = transport ?? new FakeTransport(),
TokenStore = tokenStore ?? new FakeTokenStore(),
DeviceIdProvider = new FakeDeviceIdProvider(),
RealtimeTransportFactory = realtimeTransportFactory ?? new FakeRealtimeTransportFactory(new FakeRealtimeTransport())
});
}
internal sealed class FakeTransport : IRudderTransport
{
public Dictionary<Type, object> Responses { get; } = new();
public string? LastMethod { get; private set; }
public string? LastPath { get; private set; }
public object? LastRequest { get; private set; }
public string? LastAccessToken { get; private set; }
public Task<TResponse> SendAsync<TRequest, TResponse>(
string method,
string path,
TRequest? request,
string? accessToken,
CancellationToken cancellationToken = default)
{
LastMethod = method;
LastPath = path;
LastRequest = request;
LastAccessToken = accessToken;
if (Responses.TryGetValue(typeof(TResponse), out var response))
return Task.FromResult((TResponse)response);
return Task.FromResult(default(TResponse)!);
}
}
internal sealed class FakeTokenStore : ITokenStore
{
public string? AccessToken { get; set; }
public string? RefreshToken { get; set; }
public string? GetAccessToken() => AccessToken;
public string? GetRefreshToken() => RefreshToken;
public void SaveTokens(string accessToken, string refreshToken)
{
AccessToken = accessToken;
RefreshToken = refreshToken;
}
public void Clear()
{
AccessToken = null;
RefreshToken = null;
}
}
private sealed class FakeDeviceIdProvider : IDeviceIdProvider
{
public string DeviceId => "device-id";
}
private sealed class FakeRealtimeTransportFactory : IRealtimeTransportFactory
{
private readonly IRealtimeTransport _transport;
public FakeRealtimeTransportFactory(IRealtimeTransport transport) => _transport = transport;
public IRealtimeTransport Create() => _transport;
}
private sealed class FakeRealtimeTransport : IRealtimeTransport
{
public Uri? Uri { get; private set; }
public bool IsConnected { get; private set; }
public event Action? Closed;
public event Action<Exception>? Error;
public event Action<ArraySegment<byte>>? Received;
public Task ConnectAsync(Uri uri, TimeSpan timeout, CancellationToken cancellationToken = default)
{
Uri = uri;
IsConnected = true;
return Task.CompletedTask;
}
public Task SendAsync(ArraySegment<byte> payload, CancellationToken cancellationToken = default)
=> Task.CompletedTask;
public Task CloseAsync(CancellationToken cancellationToken = default)
{
IsConnected = false;
Closed?.Invoke();
return Task.CompletedTask;
}
public void Update(float deltaTime)
{
}
public void EmitError(Exception ex) => Error?.Invoke(ex);
public void EmitReceived(ArraySegment<byte> data) => Received?.Invoke(data);
}
}