diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
new file mode 100644
index 0000000..6ea836a
--- /dev/null
+++ b/.gitea/workflows/ci.yaml
@@ -0,0 +1,47 @@
+name: CI
+
+on:
+ push:
+ branches: [main, master]
+ tags: ['v*']
+ pull_request:
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.0.x'
+
+ - name: Build
+ run: dotnet build Rudder.Core.csproj -c Release
+
+ - name: Test
+ run: dotnet test tests/Rudder.Core.Tests/Rudder.Core.Tests.csproj -c Release
+
+ publish:
+ if: startsWith(github.ref, 'refs/tags/v')
+ needs: check
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.0.x'
+
+ - name: Pack
+ run: dotnet pack Rudder.Core.csproj -c Release -o artifacts
+
+ - name: Add Gitea NuGet source
+ run: dotnet nuget add source --name gitea --username ${{ github.actor }} --password ${{ secrets.GITEA_TOKEN }} --store-password-in-clear-text https://hub.rudder.build/api/packages/rudder/nuget/index.json
+
+ - name: Publish
+ run: dotnet nuget push artifacts/*.nupkg --source gitea
diff --git a/Abstractions/IUploadTransport.cs b/Abstractions/IUploadTransport.cs
deleted file mode 100644
index b403c0a..0000000
--- a/Abstractions/IUploadTransport.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace RudderSdk.Core.Abstractions;
-
-///
-/// Uploads raw bytes to pre-signed UGC URLs. Optional; required only by
-/// .
-///
-public interface IUploadTransport
-{
- /// PUTs the payload to the pre-signed URL with the given content type.
- Task PutAsync(string url, byte[] data, string contentType, CancellationToken cancellationToken = default);
-}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6a07c8a..348a382 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,40 @@
# Changelog
+## 0.3.0
+
+### Removed
+
+- UGC surface: `UgcService` (`client.Ugc`), the generated
+ `RudderSdk.Core.Models.Ugc` DTOs, `IUploadTransport` and
+ `RudderClientOptions.UploadTransport`. The server no longer exposes the UGC
+ endpoints.
+
+### Changed (breaking)
+
+- `BattlePassService` simple overloads sent empty scenario/node ids and always
+ failed server-side. Battle pass state is tied to a scenario battle pass
+ node, so the public API now mirrors the web SDK: `GetProgressAsync(scenarioId,
+ nodeId)` and the request-DTO overloads `AddXpAsync`, `ClaimRewardAsync`,
+ `PurchasePremiumAsync` (carrying `ScenarioId`/`NodeId`/`RunId`) are public;
+ the internal request overloads are gone.
+- `BattlePassSession` now exposes the bound battle pass operations
+ (`GetProgressAsync`, `AddXpAsync(source, amount)`, `ClaimRewardAsync(level,
+ track)`, `PurchasePremiumAsync()` — crosses `onPremiumPurchase` on success),
+ mirroring the web SDK session.
+- `BattlePassLevelSession.CompleteAsync`/`Complete` renamed to
+ `ClaimAsync`/`Claim`; new `Level` property reads the `levelNumber` node data
+ key.
+- Unsupported scenario node types now fail the run (`OnScenarioFailed`)
+ instead of stalling silently.
+- Scenario counter updates no longer read a continuation plan from the
+ response (the server stopped returning one); when the server reports the
+ objective completed, the node crosses `onComplete`.
+
+### Fixed
+
+- A failed scenario counter update no longer fails the run; the error is
+ logged and the node stays active.
+
## 0.2.0
### Added
diff --git a/README.md b/README.md
index 7bd970e..e1775e5 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
.NET client SDK for the Rudder LiveOps platform: authentication, player
profile, stores, battle pass, quests, leaderboards, inventory, remote config,
-scenarios, storage, UGC and realtime.
+scenarios, storage and realtime.
- Target framework: `netstandard2.1` (works in Unity, .NET, Xamarin).
- JSON: Newtonsoft.Json.
@@ -60,7 +60,6 @@ stack.
| `RemoteConfig` | `RemoteConfigService` | `LoadAsync`, `Get`, `GetAsync` |
| `Scenario` | `ScenarioService` | `TriggerAsync`, `RestoreAsync`, `On*` effect events |
| `Storage` | `StorageService` | `GetAsync`, `ListAllAsync`, `SaveAsync`, `DeleteAsync` |
-| `Ugc` | `UgcService` | `UploadAsync`, `ListAsync`, `ListAllAsync`, `GetDownloadUrlAsync` |
| `Realtime` | `RealtimeService` | `ConnectAsync`, `DisconnectAsync` |
## Sessions
diff --git a/Rudder.Core.csproj b/Rudder.Core.csproj
index f24a32d..f2e74e3 100644
--- a/Rudder.Core.csproj
+++ b/Rudder.Core.csproj
@@ -6,9 +6,9 @@
Rudder.Core
RudderSdk.Core
Rudder.Core
- 0.2.0
+ 0.3.0
Rudder
- Rudder LiveOps client SDK for .NET: auth, player, stores, battle pass, quests, leaderboards, inventory, remote config, scenarios, storage, UGC and realtime.
+ Rudder LiveOps client SDK for .NET: auth, player, stores, battle pass, quests, leaderboards, inventory, remote config, scenarios, storage and realtime.
MIT
true
diff --git a/RudderClient.cs b/RudderClient.cs
index a1ccfe5..4e366f5 100644
--- a/RudderClient.cs
+++ b/RudderClient.cs
@@ -47,9 +47,6 @@ public sealed class RudderClient
/// Global quests.
public QuestsService Quests { get; }
- /// User-generated content.
- public UgcService Ugc { get; }
-
/// Scenario runtime: triggers, node sessions, persistence.
public ScenarioService Scenario { get; }
@@ -93,7 +90,6 @@ public sealed class RudderClient
Stores = new StoresService(this);
Leaderboards = new LeaderboardsService(this);
Inventory = new InventoryService(this);
- Ugc = new UgcService(this);
Scenario = new ScenarioService(this);
BattlePass = new BattlePassService(this);
Quests = new QuestsService(this);
diff --git a/RudderClientOptions.cs b/RudderClientOptions.cs
index eacb979..02ccad6 100644
--- a/RudderClientOptions.cs
+++ b/RudderClientOptions.cs
@@ -21,9 +21,6 @@ public sealed class RudderClientOptions
/// HTTP transport. Defaults to .
public IRudderTransport? Transport { get; set; }
- /// Upload transport for UGC file uploads. Optional.
- public IUploadTransport? UploadTransport { get; set; }
-
/// Session token storage. Defaults to .
public ITokenStore? TokenStore { get; set; }
diff --git a/Services/BattlePassService.cs b/Services/BattlePassService.cs
index 5e60094..8ef277d 100644
--- a/Services/BattlePassService.cs
+++ b/Services/BattlePassService.cs
@@ -6,16 +6,17 @@ using RudderSdk.Core.Models.BattlePass;
namespace RudderSdk.Core;
///
-/// Battle pass progress and rewards. The simple overloads target the project's
-/// default battle pass; the request-based overloads carry the scenario node
-/// context and are used by the scenario runtime.
+/// Battle pass progress and rewards. Battle pass state is tied to a scenario
+/// battle pass node, so every call carries the scenario/node ids (and a run id
+/// for the mutating calls) — supplies them
+/// during scenario runs.
///
public sealed class BattlePassService
{
- /// Free reward track, see .
+ /// Free reward track, see .
public const string TrackFree = "free";
- /// Premium reward track, see .
+ /// Premium reward track, see .
public const string TrackPremium = "premium";
private readonly RudderClient _client;
@@ -23,55 +24,43 @@ public sealed class BattlePassService
internal BattlePassService(RudderClient client) => _client = client;
/// Reads current progress: xp, level, premium ownership, claimed tiers.
- public Task GetProgressAsync(CancellationToken cancellationToken = default)
- => GetProgressAsync(new GetBattlePassProgressRequest(), cancellationToken);
-
- /// Credits xp and returns the new xp/level and level-up flags.
- public Task AddXpAsync(long amount, CancellationToken cancellationToken = default)
- => AddXpAsync(new AddBattlePassXpRequest { Amount = amount }, cancellationToken);
-
- ///
- /// Claims a tier reward at a reached level (idempotent server-side).
- /// is or .
- ///
- public Task ClaimRewardAsync(int level, string track, CancellationToken cancellationToken = default)
- => ClaimRewardAsync(new ClaimBattlePassRewardRequest { Level = level, Track = track }, cancellationToken);
-
- ///
- /// Purchases the premium track (charges the wallet). When
- /// is null, a random one is generated.
- ///
- public Task PurchasePremiumAsync(string? idempotencyKey = null, CancellationToken cancellationToken = default)
- => PurchasePremiumAsync(new PurchaseBattlePassPremiumRequest
- {
- IdempotencyKey = idempotencyKey ?? Guid.NewGuid().ToString()
- }, cancellationToken);
-
- internal Task GetProgressAsync(GetBattlePassProgressRequest request, CancellationToken cancellationToken = default)
+ public Task GetProgressAsync(string scenarioId, string nodeId, CancellationToken cancellationToken = default)
=> _client.SendAsync(
"POST",
"/sdk/v1/battlepass/progress",
- request,
+ new GetBattlePassProgressRequest { ScenarioId = scenarioId, NodeId = nodeId },
cancellationToken);
- internal Task AddXpAsync(AddBattlePassXpRequest request, CancellationToken cancellationToken = default)
+ /// Credits xp and returns the new xp/level and level-up flags.
+ public Task AddXpAsync(AddBattlePassXpRequest request, CancellationToken cancellationToken = default)
=> _client.SendAsync(
"POST",
"/sdk/v1/battlepass/xp",
request,
cancellationToken);
- internal Task ClaimRewardAsync(ClaimBattlePassRewardRequest request, CancellationToken cancellationToken = default)
+ ///
+ /// Claims a tier reward at a reached level (idempotent server-side).
+ /// is or .
+ ///
+ public Task ClaimRewardAsync(ClaimBattlePassRewardRequest request, CancellationToken cancellationToken = default)
=> _client.SendAsync(
"POST",
"/sdk/v1/battlepass/claim",
request,
cancellationToken);
- internal Task PurchasePremiumAsync(PurchaseBattlePassPremiumRequest request, CancellationToken cancellationToken = default)
- => _client.SendAsync(
+ ///
+ /// Purchases the premium track (charges the wallet). When
+ /// is null, a random one is generated.
+ ///
+ public Task PurchasePremiumAsync(PurchaseBattlePassPremiumRequest request, CancellationToken cancellationToken = default)
+ {
+ request.IdempotencyKey ??= Guid.NewGuid().ToString();
+ return _client.SendAsync(
"POST",
"/sdk/v1/battlepass/premium",
request,
cancellationToken);
+ }
}
diff --git a/Services/ScenarioService.cs b/Services/ScenarioService.cs
index 154887c..c979df6 100644
--- a/Services/ScenarioService.cs
+++ b/Services/ScenarioService.cs
@@ -227,12 +227,16 @@ public sealed partial class ScenarioService
},
cancellationToken).ConfigureAwait(false);
- if (response?.Plan != null)
- StartPlan(response.Plan);
+ // The server reports objective completion; it no longer returns a plan from the
+ // counter endpoint. On completion, cross the node's onComplete handle (which
+ // advances the run) — idempotent if the consumer also completes the session.
+ if (response != null && response.Completed)
+ await CompleteNodeAsync(runId, nodeId, "onComplete", cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
- FailRun(run, nodeId, ex);
+ // Counter update failure does not fail the run.
+ _client.Options.Logger?.Log(RudderLogLevel.Warning, $"[Rudder] Scenario counter update failed at node {nodeId}: {ex.Message}");
}
}
}
diff --git a/Services/Scenarios/ScenarioService.Dispatch.cs b/Services/Scenarios/ScenarioService.Dispatch.cs
index 189480e..ecd7a83 100644
--- a/Services/Scenarios/ScenarioService.Dispatch.cs
+++ b/Services/Scenarios/ScenarioService.Dispatch.cs
@@ -47,7 +47,10 @@ public sealed partial class ScenarioService
EmitBattlePassLevel(context);
break;
default:
+ // Unsupported node type — fail the run (surfaced via OnScenarioFailed)
+ // instead of leaving it stalled on a node no handler will complete.
_client.Options.Logger?.Log(RudderLogLevel.Warning, $"[Rudder] Unsupported scenario node type '{node.Type}' ({node.Id}).");
+ FailRun(run, state.NodeId, new Exception($"Unsupported scenario node type '{node.Type}'"));
break;
}
}
@@ -124,7 +127,7 @@ public sealed partial class ScenarioService
private void EmitBattlePass(ScenarioNodeContext context)
{
- OnBattlePass?.Invoke(new BattlePassSession(context));
+ OnBattlePass?.Invoke(new BattlePassSession(context, _client.BattlePass));
}
private void EmitBattlePassLevel(ScenarioNodeContext context)
diff --git a/Services/Scenarios/Sessions/BattlePassLevelSession.cs b/Services/Scenarios/Sessions/BattlePassLevelSession.cs
index 576a68b..7694385 100644
--- a/Services/Scenarios/Sessions/BattlePassLevelSession.cs
+++ b/Services/Scenarios/Sessions/BattlePassLevelSession.cs
@@ -3,7 +3,11 @@ using System.Threading.Tasks;
namespace RudderSdk.Core;
-/// Session of a scenario battle-pass-level node.
+///
+/// Session of a scenario battle-pass-level node — a single claimable tier.
+/// crosses onComplete, which the server accepts only
+/// once the player has reached the node's configured level.
+///
public sealed class BattlePassLevelSession
{
internal BattlePassLevelSession(ScenarioNodeContext context) => Context = context;
@@ -14,12 +18,15 @@ public sealed class BattlePassLevelSession
/// Node id.
public string Id => Context.NodeId;
+ /// The tier level this node claims.
+ public int Level => Context.Get("levelNumber", 0);
+
/// Reads a typed value from the node data.
public T Get(string key, T defaultValue = default!) => Context.Get(key, defaultValue);
- /// Advances the run through the onComplete handle.
- public Task CompleteAsync(CancellationToken cancellationToken = default) => Context.CompleteAsync("onComplete", cancellationToken);
+ /// Claims this tier; crosses onComplete (the server checks the level was reached).
+ public Task ClaimAsync(CancellationToken cancellationToken = default) => Context.CompleteAsync("onComplete", cancellationToken);
- /// Advances the run through the onComplete handle (fire-and-forget).
- public void Complete() => Context.Complete("onComplete");
+ /// Claims this tier (fire-and-forget).
+ public void Claim() => Context.Complete("onComplete");
}
diff --git a/Services/Scenarios/Sessions/BattlePassSession.cs b/Services/Scenarios/Sessions/BattlePassSession.cs
index 6c98864..9203b0f 100644
--- a/Services/Scenarios/Sessions/BattlePassSession.cs
+++ b/Services/Scenarios/Sessions/BattlePassSession.cs
@@ -1,12 +1,24 @@
+using System;
using System.Threading;
using System.Threading.Tasks;
+using RudderSdk.Core.Models.BattlePass;
namespace RudderSdk.Core;
-/// Session of a scenario battle-pass node.
+///
+/// Session of a scenario battle-pass node. Exposes the battle pass operations
+/// bound to this node's scenario/node/run ids, plus explicit boundary crossings
+/// the game drives from its UI; the server validates each crossing.
+///
public sealed class BattlePassSession
{
- internal BattlePassSession(ScenarioNodeContext context) => Context = context;
+ private readonly BattlePassService _battlePass;
+
+ internal BattlePassSession(ScenarioNodeContext context, BattlePassService battlePass)
+ {
+ Context = context;
+ _battlePass = battlePass;
+ }
/// Underlying node context.
public ScenarioNodeContext Context { get; }
@@ -17,6 +29,52 @@ public sealed class BattlePassSession
/// Reads a typed value from the node data.
public T Get(string key, T defaultValue = default!) => Context.Get(key, defaultValue);
+ /// Reads current progress (xp, level, premium ownership, claimed tiers) for this node.
+ public Task GetProgressAsync(CancellationToken cancellationToken = default)
+ => _battlePass.GetProgressAsync(Context.ScenarioId, Context.NodeId, cancellationToken);
+
+ /// Credits xp from a configured source.
+ public Task AddXpAsync(string source, long amount, CancellationToken cancellationToken = default)
+ => _battlePass.AddXpAsync(new AddBattlePassXpRequest
+ {
+ ScenarioId = Context.ScenarioId,
+ NodeId = Context.NodeId,
+ RunId = Context.RunId,
+ Source = source,
+ Amount = amount
+ }, cancellationToken);
+
+ ///
+ /// Claims a tier reward at a reached level.
+ /// is or .
+ ///
+ public Task ClaimRewardAsync(int level, string track, CancellationToken cancellationToken = default)
+ => _battlePass.ClaimRewardAsync(new ClaimBattlePassRewardRequest
+ {
+ ScenarioId = Context.ScenarioId,
+ NodeId = Context.NodeId,
+ RunId = Context.RunId,
+ Level = level,
+ Track = track
+ }, cancellationToken);
+
+ /// Purchases the premium track, then crosses onPremiumPurchase on success.
+ public async Task PurchasePremiumAsync(CancellationToken cancellationToken = default)
+ {
+ var response = await _battlePass.PurchasePremiumAsync(new PurchaseBattlePassPremiumRequest
+ {
+ ScenarioId = Context.ScenarioId,
+ NodeId = Context.NodeId,
+ RunId = Context.RunId,
+ IdempotencyKey = Guid.NewGuid().ToString()
+ }, cancellationToken).ConfigureAwait(false);
+
+ if (response != null && response.Success)
+ await Context.CompleteAsync("onPremiumPurchase", cancellationToken).ConfigureAwait(false);
+
+ return response!;
+ }
+
/// Advances the run through the onLevelUp handle.
public Task LevelUpAsync(CancellationToken cancellationToken = default) => Context.CompleteAsync("onLevelUp", cancellationToken);
@@ -29,12 +87,6 @@ public sealed class BattlePassSession
/// Advances the run through the onMaxLevel handle (fire-and-forget).
public void MaxLevel() => Context.Complete("onMaxLevel");
- /// Advances the run through the onPremiumPurchase handle.
- public Task PremiumPurchaseAsync(CancellationToken cancellationToken = default) => Context.CompleteAsync("onPremiumPurchase", cancellationToken);
-
- /// Advances the run through the onPremiumPurchase handle (fire-and-forget).
- public void PremiumPurchase() => Context.Complete("onPremiumPurchase");
-
/// Advances the run through the onComplete handle.
public Task CompleteAsync(CancellationToken cancellationToken = default) => Context.CompleteAsync("onComplete", cancellationToken);
diff --git a/Services/UgcService.cs b/Services/UgcService.cs
deleted file mode 100644
index 107302c..0000000
--- a/Services/UgcService.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-using Newtonsoft.Json.Linq;
-using RudderSdk.Core.Models.Ugc;
-
-namespace RudderSdk.Core;
-
-/// User-generated content: upload, list, download URLs, deletion.
-public sealed class UgcService
-{
- private readonly RudderClient _client;
-
- internal UgcService(RudderClient client) => _client = client;
-
- ///
- /// Uploads a file in one call: pre-signed URL, PUT of the bytes, submission.
- /// Requires an upload transport in .
- ///
- public async Task UploadAsync(
- string name,
- byte[] data,
- string? description = null,
- JToken? metadata = null,
- CancellationToken cancellationToken = default)
- {
- var uploadTransport = _client.Options.UploadTransport
- ?? throw new InvalidOperationException("UploadTransport is not configured.");
-
- var upload = await GetUploadUrlAsync(name, cancellationToken).ConfigureAwait(false);
- await uploadTransport.PutAsync(
- upload.UploadUrl,
- data ?? Array.Empty(),
- GetContentType(name),
- cancellationToken).ConfigureAwait(false);
-
- return await SubmitAsync(
- name,
- upload.FileKey,
- data?.LongLength ?? 0,
- description,
- metadata,
- cancellationToken).ConfigureAwait(false);
- }
-
- /// Fetches one page of the player's submissions.
- public Task ListAsync(string? status = null, int limit = 20, string? cursor = null, CancellationToken cancellationToken = default)
- => _client.SendAsync(
- "GET",
- "/sdk/v1/ugc" + Url.Query(
- ("status", status),
- ("limit", limit > 0 ? limit.ToString() : null),
- ("cursor", cursor)),
- cancellationToken);
-
- /// Iterates over all of the player's submissions, following the cursor pagination.
- public async IAsyncEnumerable ListAllAsync(
- string? status = null,
- int limit = 20,
- [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- string? cursor = null;
- do
- {
- var page = await ListAsync(status, limit, cursor, cancellationToken).ConfigureAwait(false);
- if (page?.Items != null)
- {
- foreach (var item in page.Items)
- yield return item;
- }
-
- cursor = page?.NextCursor;
- }
- while (!string.IsNullOrEmpty(cursor));
- }
-
- /// Returns one submission.
- public Task GetAsync(string id, CancellationToken cancellationToken = default)
- => _client.SendAsync("GET", "/sdk/v1/ugc/" + Url.Encode(id), cancellationToken);
-
- /// Deletes a submission; returns false when the server refused.
- public async Task DeleteAsync(string id, CancellationToken cancellationToken = default)
- {
- var response = await _client.SendAsync(
- "DELETE",
- "/sdk/v1/ugc/" + Url.Encode(id),
- cancellationToken).ConfigureAwait(false);
-
- return response == null || response.Success;
- }
-
- /// Requests a pre-signed upload URL.
- public Task GetUploadUrlAsync(string filename, CancellationToken cancellationToken = default)
- => _client.SendAsync(
- "GET",
- "/sdk/v1/ugc/upload-url" + Url.Query(("filename", filename)),
- cancellationToken);
-
- ///
- /// Resolves the download URL of a submission.
- /// Throws when the server returned none.
- ///
- public async Task GetDownloadUrlAsync(string id, CancellationToken cancellationToken = default)
- {
- var response = await _client.SendAsync(
- "GET",
- "/sdk/v1/ugc/" + Url.Encode(id) + "/download",
- cancellationToken).ConfigureAwait(false);
-
- if (response == null || string.IsNullOrEmpty(response.DownloadUrl))
- throw new InvalidOperationException("The server returned no download URL.");
-
- return response.DownloadUrl;
- }
-
- /// Registers an uploaded file as a submission.
- public Task SubmitAsync(
- string name,
- string fileKey,
- long fileSize,
- string? description = null,
- JToken? metadata = null,
- CancellationToken cancellationToken = default)
- {
- return _client.SendAsync(
- "POST",
- "/sdk/v1/ugc",
- new SubmitUgcRequest
- {
- Name = name,
- FileKey = fileKey,
- FileSize = fileSize,
- Description = description!,
- Metadata = metadata!
- },
- cancellationToken);
- }
-
- private static string GetContentType(string filename)
- {
- var ext = System.IO.Path.GetExtension(filename)?.ToLowerInvariant();
- switch (ext)
- {
- case ".png": return "image/png";
- case ".jpg":
- case ".jpeg": return "image/jpeg";
- case ".gif": return "image/gif";
- case ".webp": return "image/webp";
- case ".txt": return "text/plain";
- default: return "application/octet-stream";
- }
- }
-}
diff --git a/Ugc/DeleteUgcResponse.cs b/Ugc/DeleteUgcResponse.cs
deleted file mode 100644
index 17f68b3..0000000
--- a/Ugc/DeleteUgcResponse.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Code generated by apigen. DO NOT EDIT.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Collections.Generic;
-
-namespace RudderSdk.Core.Models.Ugc;
-
-public class DeleteUgcResponse
-{
- [JsonProperty("success")]
- public bool Success { get; set; }
-
-}
diff --git a/Ugc/GetDownloadUrlResponse.cs b/Ugc/GetDownloadUrlResponse.cs
deleted file mode 100644
index a7a832d..0000000
--- a/Ugc/GetDownloadUrlResponse.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Code generated by apigen. DO NOT EDIT.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Collections.Generic;
-
-namespace RudderSdk.Core.Models.Ugc;
-
-public class GetDownloadUrlResponse
-{
- [JsonProperty("downloadUrl")]
- public string DownloadUrl { get; set; }
-
-}
diff --git a/Ugc/GetUploadUrlResponse.cs b/Ugc/GetUploadUrlResponse.cs
deleted file mode 100644
index 148c485..0000000
--- a/Ugc/GetUploadUrlResponse.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// Code generated by apigen. DO NOT EDIT.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Collections.Generic;
-
-namespace RudderSdk.Core.Models.Ugc;
-
-public class GetUploadUrlResponse
-{
- [JsonProperty("fileKey")]
- public string FileKey { get; set; }
-
- [JsonProperty("uploadUrl")]
- public string UploadUrl { get; set; }
-
-}
diff --git a/Ugc/ListUgcResponse.cs b/Ugc/ListUgcResponse.cs
deleted file mode 100644
index de05f5b..0000000
--- a/Ugc/ListUgcResponse.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// Code generated by apigen. DO NOT EDIT.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Collections.Generic;
-
-namespace RudderSdk.Core.Models.Ugc;
-
-public class ListUgcResponse
-{
- [JsonProperty("items")]
- public List Items { get; set; }
-
- [JsonProperty("nextCursor")]
- public string NextCursor { get; set; }
-
-}
diff --git a/Ugc/SubmitUgcRequest.cs b/Ugc/SubmitUgcRequest.cs
deleted file mode 100644
index 6dd0b8f..0000000
--- a/Ugc/SubmitUgcRequest.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Code generated by apigen. DO NOT EDIT.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Collections.Generic;
-
-namespace RudderSdk.Core.Models.Ugc;
-
-public class SubmitUgcRequest
-{
- [JsonProperty("description")]
- public string Description { get; set; }
-
- [JsonProperty("fileKey")]
- public string FileKey { get; set; }
-
- [JsonProperty("fileSize")]
- public long FileSize { get; set; }
-
- [JsonProperty("metadata")]
- public JToken Metadata { get; set; }
-
- [JsonProperty("name")]
- public string Name { get; set; }
-
-}
diff --git a/Ugc/SubmittedBy.cs b/Ugc/SubmittedBy.cs
deleted file mode 100644
index 01f5fb5..0000000
--- a/Ugc/SubmittedBy.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// Code generated by apigen. DO NOT EDIT.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Collections.Generic;
-
-namespace RudderSdk.Core.Models.Ugc;
-
-public class SubmittedBy
-{
- [JsonProperty("userId")]
- public string UserId { get; set; }
-
- [JsonProperty("username")]
- public string Username { get; set; }
-
-}
diff --git a/Ugc/UgcSubmission.cs b/Ugc/UgcSubmission.cs
deleted file mode 100644
index a9b94af..0000000
--- a/Ugc/UgcSubmission.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-// Code generated by apigen. DO NOT EDIT.
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using System.Collections.Generic;
-
-namespace RudderSdk.Core.Models.Ugc;
-
-public class UgcSubmission
-{
- [JsonProperty("description")]
- public string Description { get; set; }
-
- [JsonProperty("fileSize")]
- public long FileSize { get; set; }
-
- [JsonProperty("fileUrl")]
- public string FileUrl { get; set; }
-
- [JsonProperty("id")]
- public string Id { get; set; }
-
- [JsonProperty("metadata")]
- public JToken Metadata { get; set; }
-
- [JsonProperty("name")]
- public string Name { get; set; }
-
- [JsonProperty("reviewedAt")]
- public string ReviewedAt { get; set; }
-
- [JsonProperty("reviewedBy")]
- public string ReviewedBy { get; set; }
-
- [JsonProperty("status")]
- public string Status { get; set; }
-
- [JsonProperty("submittedAt")]
- public string SubmittedAt { get; set; }
-
- [JsonProperty("submittedBy")]
- public SubmittedBy SubmittedBy { get; set; }
-
-}
diff --git a/tests/Rudder.Core.Tests/RudderClientTests.cs b/tests/Rudder.Core.Tests/RudderClientTests.cs
index a61a220..e074ccc 100644
--- a/tests/Rudder.Core.Tests/RudderClientTests.cs
+++ b/tests/Rudder.Core.Tests/RudderClientTests.cs
@@ -22,7 +22,6 @@ public sealed class RudderClientTests
Assert.NotNull(client.Inventory);
Assert.NotNull(client.BattlePass);
Assert.NotNull(client.Quests);
- Assert.NotNull(client.Ugc);
Assert.NotNull(client.Scenario);
Assert.NotNull(client.Realtime);
}
diff --git a/tests/Rudder.Core.Tests/ScenarioServiceTests.cs b/tests/Rudder.Core.Tests/ScenarioServiceTests.cs
index aa1170f..7c20af1 100644
--- a/tests/Rudder.Core.Tests/ScenarioServiceTests.cs
+++ b/tests/Rudder.Core.Tests/ScenarioServiceTests.cs
@@ -265,7 +265,7 @@ public sealed class ScenarioServiceTests
}
[Fact]
- public async Task Quest_Progress_Response_Can_Start_Continuation_Plan()
+ public async Task Quest_Progress_Completed_Response_Completes_The_Node()
{
var transport = new FakeTransport();
transport.Enqueue(new TriggerScenarioResponse
@@ -275,16 +275,12 @@ public sealed class ScenarioServiceTests
Plan("plan", Node("quest", "quest"))
}
});
- transport.Enqueue(new UpdateScenarioCounterResponse
- {
- Completed = true,
- Plan = Plan("continuation", Node("done", "notification"))
- });
+ transport.Enqueue(new UpdateScenarioCounterResponse { Completed = true });
var client = CreateClient(transport);
QuestSession? quest = null;
- var notifications = 0;
+ var completed = 0;
client.Scenario.OnQuest += session => quest = session;
- client.Scenario.OnNotification += _ => notifications++;
+ client.Scenario.OnScenarioCompleted += _ => completed++;
await client.Scenario.TriggerAsync("login");
await quest!.AddProgressAsync("wins", 1);
@@ -293,11 +289,40 @@ public sealed class ScenarioServiceTests
transport.Calls.Single(call => call.Path == "/sdk/v1/scenarios/counter").Request);
Assert.Equal("quest", counter.NodeId);
Assert.Equal("wins", counter.CounterKey);
- Assert.Equal(1, notifications);
+ Assert.Equal(1, completed);
+ Assert.Empty(client.Scenario.ActiveRuns);
}
[Fact]
- public async Task Unknown_Nodes_Are_Logged_And_Ignored()
+ public async Task Quest_Progress_Failure_Does_Not_Fail_The_Run()
+ {
+ var transport = new FakeTransport();
+ transport.Enqueue(new TriggerScenarioResponse
+ {
+ Plans = new List
+ {
+ Plan("plan", Node("quest", "quest"))
+ }
+ });
+ transport.Enqueue(new InvalidOperationException("boom"));
+ var logger = new FakeLogger();
+ var client = CreateClient(transport, logger: logger);
+ QuestSession? quest = null;
+ var failed = 0;
+ client.Scenario.OnQuest += session => quest = session;
+ client.Scenario.OnScenarioFailed += _ => failed++;
+
+ await client.Scenario.TriggerAsync("login");
+ await quest!.AddProgressAsync("wins", 1);
+
+ Assert.Equal(0, failed);
+ Assert.Single(client.Scenario.ActiveRuns);
+ Assert.Contains(logger.Messages, m =>
+ m.Level == RudderLogLevel.Warning && m.Message.Contains("counter update failed"));
+ }
+
+ [Fact]
+ public async Task Unknown_Nodes_Fail_The_Run()
{
var transport = new FakeTransport();
transport.Enqueue(new TriggerScenarioResponse
@@ -309,12 +334,16 @@ public sealed class ScenarioServiceTests
});
var logger = new FakeLogger();
var client = CreateClient(transport, logger: logger);
+ ScenarioFailedEvent? failure = null;
+ client.Scenario.OnScenarioFailed += e => failure = e;
await client.Scenario.TriggerAsync("login");
- var (level, message) = Assert.Single(logger.Messages);
- Assert.Equal(RudderLogLevel.Warning, level);
- Assert.Contains("Unsupported scenario node type 'future_node'", message);
+ Assert.NotNull(failure);
+ Assert.Equal("future", failure!.NodeId);
+ Assert.Empty(client.Scenario.ActiveRuns);
+ Assert.Contains(logger.Messages, m =>
+ m.Level == RudderLogLevel.Warning && m.Message.Contains("Unsupported scenario node type 'future_node'"));
}
private static RudderClient CreateClient(