From 2a8d5d4f2ff9d6363fe98efb7e5bef632ceb12dd Mon Sep 17 00:00:00 2001 From: edmand46 Date: Wed, 19 Aug 2026 17:48:58 +0300 Subject: [PATCH] 0.3.0: UGC removed, battlepass context-carrying API, scenario behavior aligned with web SDK, CI publish --- .gitea/workflows/ci.yaml | 47 ++++++ Abstractions/IUploadTransport.cs | 14 -- CHANGELOG.md | 35 ++++ README.md | 3 +- Rudder.Core.csproj | 4 +- RudderClient.cs | 4 - RudderClientOptions.cs | 3 - Services/BattlePassService.cs | 59 +++---- Services/ScenarioService.cs | 10 +- .../Scenarios/ScenarioService.Dispatch.cs | 5 +- .../Sessions/BattlePassLevelSession.cs | 17 +- .../Scenarios/Sessions/BattlePassSession.cs | 68 +++++++- Services/UgcService.cs | 155 ------------------ Ugc/DeleteUgcResponse.cs | 13 -- Ugc/GetDownloadUrlResponse.cs | 13 -- Ugc/GetUploadUrlResponse.cs | 16 -- Ugc/ListUgcResponse.cs | 16 -- Ugc/SubmitUgcRequest.cs | 25 --- Ugc/SubmittedBy.cs | 16 -- Ugc/UgcSubmission.cs | 43 ----- tests/Rudder.Core.Tests/RudderClientTests.cs | 1 - .../Rudder.Core.Tests/ScenarioServiceTests.cs | 55 +++++-- 22 files changed, 234 insertions(+), 388 deletions(-) create mode 100644 .gitea/workflows/ci.yaml delete mode 100644 Abstractions/IUploadTransport.cs delete mode 100644 Services/UgcService.cs delete mode 100644 Ugc/DeleteUgcResponse.cs delete mode 100644 Ugc/GetDownloadUrlResponse.cs delete mode 100644 Ugc/GetUploadUrlResponse.cs delete mode 100644 Ugc/ListUgcResponse.cs delete mode 100644 Ugc/SubmitUgcRequest.cs delete mode 100644 Ugc/SubmittedBy.cs delete mode 100644 Ugc/UgcSubmission.cs 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(