From e88cb60ea5c3e359ff99cf0ce05ad7e8b1e6f350 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 09:07:09 +0300 Subject: [PATCH 1/3] chore(types): regenerate hub types from gateway refactoringV2 81f87e446 (runs per function) GetMarketplaceFunctionRuns gains functionId (one of functionId / triggerId / taskId); MarketplaceFunctionRunDto gains status, correlationId, hasDetails, triggerId and taskId. Additive only; Api types unchanged. Co-Authored-By: Claude Opus 5.5 --- src/Norbix.Sdk.Types/Generated/Hub.dtos.cs | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs index f9027d3..0e921cc 100644 --- a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs +++ b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs @@ -3901,6 +3901,9 @@ public partial class MarketplaceFunctionRunDto [DataMember] public virtual bool Succeeded { get; set; } + [DataMember] + public virtual string? Status { get; set; } + [DataMember] public virtual long? DurationMs { get; set; } @@ -3910,6 +3913,18 @@ public partial class MarketplaceFunctionRunDto [DataMember] public virtual string? VendorRequestId { get; set; } + [DataMember] + public virtual string? CorrelationId { get; set; } + + [DataMember] + public virtual bool HasDetails { get; set; } + + [DataMember] + public virtual string? TriggerId { get; set; } + + [DataMember] + public virtual string? TaskId { get; set; } + [DataMember] public virtual string? Input { get; set; } } @@ -11422,12 +11437,17 @@ public partial class GetMarketplaceFunctionRuns : CodeMashRequestBase, INorbixRequest { /// - ///The trigger whose marketplace function runs to list (trg_…). One of triggerId / taskId. + ///The marketplace function whose runs to list, from every source (func_…). One of functionId / triggerId / taskId. + /// + public virtual string? FunctionId { get; set; } + + /// + ///The trigger whose marketplace function runs to list (trg_…). One of functionId / triggerId / taskId. /// public virtual string? TriggerId { get; set; } /// - ///The scheduler task whose marketplace function runs to list (tsk_…). One of triggerId / taskId. + ///The scheduler task whose marketplace function runs to list (tsk_…). One of functionId / triggerId / taskId. /// public virtual string? TaskId { get; set; } From 805d7b8f8c5d821b82bd448e5e9dc62d9cbceeb1 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 09:11:33 +0300 Subject: [PATCH 2/3] feat(marketplace): runs per function (FunctionId) and GetMarketplaceFunctionRunDetailsAsync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code.GetMarketplaceFunctionRunsAsync sends FunctionId from the regenerated request (one of FunctionId / TriggerId / TaskId); rows carry Status, CorrelationId, HasDetails, TriggerId and TaskId. NorbixHubClient gains GetMarketplaceFunctionRunDetailsAsync(correlationId), a wrapper over Logs.GetLogsByCorrelationIdAsync (GET /{version}/logs/audit?targetCorrelationId=…). MarketplaceRunsTests pins both queries; the Code coverage snapshot is accepted (functionId on the query, HasDetails on the row). README section added. Co-Authored-By: Claude Opus 5.5 --- README.md | 28 ++++++ src/Norbix.Hub/NorbixHubClient.Convenience.cs | 22 ++++- .../Norbix.Hub.Tests/MarketplaceRunsTests.cs | 89 +++++++++++++++++++ ...ndpointCoverageTests.Hub.Code.verified.txt | 5 +- 4 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs diff --git a/README.md b/README.md index 2aaca34..dfade1d 100644 --- a/README.md +++ b/README.md @@ -1000,6 +1000,34 @@ provider (the payload is the project's data) and when the function was not called. There is no default Code integration: every call names the integration it means (`IntegrationId`), as the examples above do. +## Marketplace function runs + +Each call of a marketplace function is one run row: an invoke (user, API +key, AI), a trigger run or a scheduler fire. +`Code.GetMarketplaceFunctionRunsAsync` lists them — set exactly one of +`FunctionId` (every source, newest first, up to 50), `TriggerId` or +`TaskId`. A row carries `Status` (`ok` / `failed` / `skipped`, `null` on +rows written before the field existed), `DurationMs`, `Source`, `Event`, +`CorrelationId` and `HasDetails`. + +The input, steps and response of a run are written to the project log under +the row's `CorrelationId`, only while the environment's Norbix Logging is on +— then `HasDetails` is `true`. `GetMarketplaceFunctionRunDetailsAsync` (in +`Norbix.Hub`, a hand-written helper over `Logs.GetLogsByCorrelationIdAsync`, +`GET /{version}/logs/audit?targetCorrelationId=…`) reads them: + +```csharp +var runs = await hub.Code.GetMarketplaceFunctionRunsAsync( + new GetMarketplaceFunctionRuns { FunctionId = "func_…", Limit = 10 }); + +foreach (var run in runs?.Runs ?? []) +{ + if (!run.HasDetails || run.CorrelationId is null) continue; // logs were off + var details = await hub.GetMarketplaceFunctionRunDetailsAsync(run.CorrelationId); + // details.Items: marketplace:run:started → marketplace:run:step … → completed | failed | skipped +} +``` + ## Coverage Notes Generated coverage tracks the API and Hub DTO contract files. Some flows are not generated automatically: diff --git a/src/Norbix.Hub/NorbixHubClient.Convenience.cs b/src/Norbix.Hub/NorbixHubClient.Convenience.cs index e74be0b..8d094d9 100644 --- a/src/Norbix.Hub/NorbixHubClient.Convenience.cs +++ b/src/Norbix.Hub/NorbixHubClient.Convenience.cs @@ -23,5 +23,25 @@ public sealed partial class NorbixHubClient }, cancellationToken); } -} + /// + /// The details of one marketplace function run: the project log lines filed + /// under the run's correlation id (marketplace:run:started, step, + /// then completed, failed or skipped). Pass + /// of a row from + /// Code.GetMarketplaceFunctionRunsAsync. Only a row with + /// has lines — they are + /// written only while the environment's Norbix Logging is on. + /// Same call as Logs.GetLogsByCorrelationIdAsync + /// (GET /{version}/logs/audit?targetCorrelationId=…). + /// + public Task GetMarketplaceFunctionRunDetailsAsync( + string correlationId, + CancellationToken cancellationToken = default) + { + ArgumentException.ThrowIfNullOrWhiteSpace(correlationId); + return Logs.GetLogsByCorrelationIdAsync( + new GetLogsByCorrelationId { TargetCorrelationId = correlationId }, + cancellationToken); + } +} diff --git a/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs b/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs new file mode 100644 index 0000000..5c085e6 --- /dev/null +++ b/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs @@ -0,0 +1,89 @@ +using Norbix.Sdk; +using Norbix.Sdk.Tests.Helpers; +using Norbix.Sdk.Types.Hub; + +using NUnit.Framework; + +namespace Norbix.Hub.Tests; + +/// +/// GET /{version}/code/marketplace/functions/runs takes exactly one of +/// functionId / triggerId / taskId. The coverage snapshot fills every field at +/// once; these cases pin a function's own runs (functionId alone, on the query, +/// no body) and the details helper over GET /{version}/logs/audit. +/// Nothing leaves the process. +/// +[TestFixture] +public sealed class MarketplaceRunsTests +{ + private const string CorrelationId = "0f8e2c9a4b7d4e1f9a3c5b6d7e8f9012"; + + private static NorbixTestFixture NewFixture() => + NorbixTestFixture.Create(o => + { + o.AccountId = "test-account"; + o.BearerToken = "test-bearer"; + }); + + [Test] + public async Task Function_runs_send_functionId_and_limit_on_the_query_with_no_body() + { + using var fixture = NewFixture(); + fixture.RespondNext(new + { + runs = new[] + { + new + { + functionId = "func_1p4R3gTobGZpD6fXvHT4rd", + source = "api-key", + @event = "invoke", + succeeded = true, + status = "ok", + correlationId = CorrelationId, + hasDetails = true, + }, + }, + }); + + var response = await fixture.Client.Code.GetMarketplaceFunctionRunsAsync( + new GetMarketplaceFunctionRuns { FunctionId = "func_1p4R3gTobGZpD6fXvHT4rd", Limit = 50 }); + + var sent = fixture.LastRequest!; + Assert.That(sent.Method, Is.EqualTo("GET")); + Assert.That(sent.Path, Is.EqualTo("/v2/code/marketplace/functions/runs")); + Assert.That(sent.Query, Is.EqualTo("?functionId=func_1p4R3gTobGZpD6fXvHT4rd&limit=50")); + Assert.That(sent.Body, Is.Null); + + var run = response!.Runs!.Single(); + Assert.That(run.Status, Is.EqualTo("ok")); + Assert.That(run.CorrelationId, Is.EqualTo(CorrelationId)); + Assert.That(run.HasDetails, Is.True); + Assert.That(run.Source, Is.EqualTo("api-key")); + Assert.That(run.Event, Is.EqualTo("invoke")); + } + + [Test] + public async Task Run_details_read_the_log_lines_by_targetCorrelationId() + { + using var fixture = NewFixture(); + + await fixture.Client.GetMarketplaceFunctionRunDetailsAsync(CorrelationId); + + var sent = fixture.LastRequest!; + Assert.That(sent.Method, Is.EqualTo("GET")); + Assert.That(sent.Path, Is.EqualTo("/v2/logs/audit")); + Assert.That(sent.Query, Is.EqualTo($"?targetCorrelationId={CorrelationId}")); + Assert.That(sent.Body, Is.Null); + Assert.That(sent.Headers["norbix-project-id"], Is.EqualTo("test-project")); + } + + [Test] + public void Run_details_refuse_an_empty_correlation_id() + { + using var fixture = NewFixture(); + + Assert.ThrowsAsync(() => fixture.Client.GetMarketplaceFunctionRunDetailsAsync(" ")); + Assert.That(fixture.RecordedRequests, Is.Empty); + } +} diff --git a/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt b/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt index 9fd9f75..d18ee68 100644 --- a/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt +++ b/tests/Norbix.Hub.Tests/test_results/EndpointCoverageTests.Hub.Code.verified.txt @@ -700,7 +700,7 @@ Sent: { Method: GET, Path: /v2/code/marketplace/functions/runs, - Query: ?triggerId={guid}&taskId={guid}&limit=1&env=test-env, + Query: ?functionId={guid}&triggerId={guid}&taskId={guid}&limit=1&env=test-env, Headers: { Authorization: Bearer test-bearer, norbix-account-id: test-account, @@ -711,7 +711,8 @@ Response: { Runs: [ { - Succeeded: false + Succeeded: false, + HasDetails: false } ], ResponseStatus: { From bb448f4eaa390d1e0704d552ebd71194c4b7aafc Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 09:20:36 +0300 Subject: [PATCH 3/3] test(marketplace): MarketplaceRunsTests assert with Verify snapshots like the other Hub tests The three cases (functionId query, details by targetCorrelationId, empty id refused before any request) now snapshot the sent request and the decoded rows into test_results/, as PushEndpointTests and SchedulerBodyVariantTests do. Co-Authored-By: Claude Opus 5.5 --- .../Norbix.Hub.Tests/MarketplaceRunsTests.cs | 40 +++++++++---------- ...mit_on_the_query_with_no_body.verified.txt | 23 +++++++++++ ..._lines_by_targetCorrelationId.verified.txt | 10 +++++ ...efuse_an_empty_correlation_id.verified.txt | 4 ++ 4 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Function_runs_send_functionId_and_limit_on_the_query_with_no_body.verified.txt create mode 100644 tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_read_the_log_lines_by_targetCorrelationId.verified.txt create mode 100644 tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_refuse_an_empty_correlation_id.verified.txt diff --git a/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs b/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs index 5c085e6..b2239ec 100644 --- a/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs +++ b/tests/Norbix.Hub.Tests/MarketplaceRunsTests.cs @@ -4,6 +4,8 @@ using NUnit.Framework; +using VerifyNUnit; + namespace Norbix.Hub.Tests; /// @@ -49,18 +51,9 @@ public async Task Function_runs_send_functionId_and_limit_on_the_query_with_no_b var response = await fixture.Client.Code.GetMarketplaceFunctionRunsAsync( new GetMarketplaceFunctionRuns { FunctionId = "func_1p4R3gTobGZpD6fXvHT4rd", Limit = 50 }); - var sent = fixture.LastRequest!; - Assert.That(sent.Method, Is.EqualTo("GET")); - Assert.That(sent.Path, Is.EqualTo("/v2/code/marketplace/functions/runs")); - Assert.That(sent.Query, Is.EqualTo("?functionId=func_1p4R3gTobGZpD6fXvHT4rd&limit=50")); - Assert.That(sent.Body, Is.Null); - - var run = response!.Runs!.Single(); - Assert.That(run.Status, Is.EqualTo("ok")); - Assert.That(run.CorrelationId, Is.EqualTo(CorrelationId)); - Assert.That(run.HasDetails, Is.True); - Assert.That(run.Source, Is.EqualTo("api-key")); - Assert.That(run.Event, Is.EqualTo("invoke")); + // The snapshot pins verb, path, the exact query (functionId + limit only) and no body, + // and the new row fields read back from the generated response. + await Verifier.Verify(new { Sent = fixture.LastRequest, response!.Runs }, VerifyConfig.VerifySettings); } [Test] @@ -70,20 +63,25 @@ public async Task Run_details_read_the_log_lines_by_targetCorrelationId() await fixture.Client.GetMarketplaceFunctionRunDetailsAsync(CorrelationId); - var sent = fixture.LastRequest!; - Assert.That(sent.Method, Is.EqualTo("GET")); - Assert.That(sent.Path, Is.EqualTo("/v2/logs/audit")); - Assert.That(sent.Query, Is.EqualTo($"?targetCorrelationId={CorrelationId}")); - Assert.That(sent.Body, Is.Null); - Assert.That(sent.Headers["norbix-project-id"], Is.EqualTo("test-project")); + // GET /v2/logs/audit?targetCorrelationId=… — the DTO property name, not "correlationId". + await Verifier.Verify(fixture.LastRequest, VerifyConfig.VerifySettings); } [Test] - public void Run_details_refuse_an_empty_correlation_id() + public async Task Run_details_refuse_an_empty_correlation_id() { using var fixture = NewFixture(); - Assert.ThrowsAsync(() => fixture.Client.GetMarketplaceFunctionRunDetailsAsync(" ")); - Assert.That(fixture.RecordedRequests, Is.Empty); + string? error = null; + try + { + await fixture.Client.GetMarketplaceFunctionRunDetailsAsync(" "); + } + catch (ArgumentException ex) + { + error = $"{ex.GetType().Name}: {ex.ParamName}"; + } + + await Verifier.Verify(new { Error = error, NothingSent = fixture.RecordedRequests.Count == 0 }, VerifyConfig.VerifySettings); } } diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Function_runs_send_functionId_and_limit_on_the_query_with_no_body.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Function_runs_send_functionId_and_limit_on_the_query_with_no_body.verified.txt new file mode 100644 index 0000000..14d4f35 --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Function_runs_send_functionId_and_limit_on_the_query_with_no_body.verified.txt @@ -0,0 +1,23 @@ +{ + Sent: { + Method: GET, + Path: /v2/code/marketplace/functions/runs, + Query: ?functionId=func_1p4R3gTobGZpD6fXvHT4rd&limit=50, + Headers: { + Authorization: Bearer test-bearer, + norbix-account-id: test-account, + norbix-project-id: test-project + } + }, + Runs: [ + { + Event: invoke, + Source: api-key, + FunctionId: func_1p4R3gTobGZpD6fXvHT4rd, + Succeeded: true, + Status: ok, + CorrelationId: Guid_1, + HasDetails: true + } + ] +} \ No newline at end of file diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_read_the_log_lines_by_targetCorrelationId.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_read_the_log_lines_by_targetCorrelationId.verified.txt new file mode 100644 index 0000000..cf664ce --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_read_the_log_lines_by_targetCorrelationId.verified.txt @@ -0,0 +1,10 @@ +{ + Method: GET, + Path: /v2/logs/audit, + Query: ?targetCorrelationId=0f8e2c9a4b7d4e1f9a3c5b6d7e8f9012, + Headers: { + Authorization: Bearer test-bearer, + norbix-account-id: test-account, + norbix-project-id: test-project + } +} \ No newline at end of file diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_refuse_an_empty_correlation_id.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_refuse_an_empty_correlation_id.verified.txt new file mode 100644 index 0000000..4489feb --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceRunsTests.Run_details_refuse_an_empty_correlation_id.verified.txt @@ -0,0 +1,4 @@ +{ + Error: ArgumentException: correlationId, + NothingSent: true +} \ No newline at end of file