From 5251d0396a8f8d95d96514a02639ca90b1bd2211 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 15:57:06 +0300 Subject: [PATCH 1/4] =?UTF-8?q?chore(types):=20regenerate=20hub=20types=20?= =?UTF-8?q?from=20gateway=20refactoringV2=20509156d57=20(access,=20routes?= =?UTF-8?q?=2025=E2=80=9327)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the access matrix, role package and custom policy types (GetMarketplaceIntegrationAccess*, SetMarketplaceIntegrationRoleAccess, SaveMarketplaceIntegrationCustomAccess*, MarketplaceAccess*Dto, MarketplaceAccessLevel) and the additive fields TestMarketplaceIntegrationResponse.accessChanges, MarketplaceAccessRoleDto.defaultNotApplied and MarketplaceValidationStepResultDto.errorCode. Co-Authored-By: Claude Opus 5.5 --- src/Norbix.Sdk.Types/Generated/Hub.dtos.cs | 237 +++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs index c456187..2188a42 100644 --- a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs +++ b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs @@ -3682,6 +3682,118 @@ public partial class GoogleCloudFunctionsCodeIntegrationDto public virtual string? Region { get; set; } } + [DataContract] + public partial class MarketplaceAccessChangesDto + { + [DataMember] + public virtual IReadOnlyList ReadOnlyAdded { get; set; } + + [DataMember] + public virtual IReadOnlyList ReadOnlyRemoved { get; set; } + + [DataMember] + public virtual IReadOnlyList NotGrantable { get; set; } + } + + [DataContract] + public partial class MarketplaceAccessCustomPolicyDto + { + [DataMember] + public virtual string PolicyId { get; set; } + + [DataMember] + public virtual string Name { get; set; } + + [DataMember] + public virtual int UsedByRoles { get; set; } + } + + [DataContract] + public partial class MarketplaceAccessFunctionDto + { + [DataMember] + public virtual string FunctionId { get; set; } + + [DataMember] + public virtual string Name { get; set; } + + [DataMember] + public virtual string FunctionKey { get; set; } + } + + public enum MarketplaceAccessLevel + { + None, + Viewer, + FunctionsOnly, + ReadOnly, + FullAccess, + Manage, + } + + [DataContract] + public partial class MarketplaceAccessPackageDto + { + [DataMember] + public virtual MarketplaceAccessLevel Kind { get; set; } + + [DataMember] + public virtual string PolicyId { get; set; } + + [DataMember] + public virtual string Name { get; set; } + + [DataMember] + public virtual bool Exists { get; set; } + } + + [DataContract] + public partial class MarketplaceAccessRoleDto + { + [DataMember] + public virtual string RoleId { get; set; } + + [DataMember] + public virtual string Name { get; set; } + + [DataMember] + public virtual string Scope { get; set; } + + [DataMember] + public virtual bool IsSystem { get; set; } + + [DataMember] + public virtual bool Fixed { get; set; } + + [DataMember] + public virtual MarketplaceAccessLevel Package { get; set; } + + [DataMember] + public virtual bool IsCustom { get; set; } + + [DataMember] + public virtual bool Manage { get; set; } + + [DataMember] + public virtual IReadOnlyList Custom { get; set; } + + [DataMember] + public virtual string? DefaultNotApplied { get; set; } + } + + [DataContract] + public partial class MarketplaceAccessToolDto + { + [DataMember] + public virtual string Name { get; set; } + + [DataMember] + public virtual bool ReadOnly { get; set; } + + [DataMember] + public virtual bool Grantable { get; set; } + } + public enum MarketplaceAvailability { Development, @@ -4234,6 +4346,9 @@ public partial class MarketplaceValidationStepResultDto [DataMember] public virtual string? Message { get; set; } + + [DataMember] + public virtual string? ErrorCode { get; set; } } public partial class AccountComplianceDto @@ -7126,6 +7241,9 @@ public partial class TestMarketplaceIntegrationResponse [DataMember] public virtual IReadOnlyList? Steps { get; set; } + + [DataMember] + public virtual MarketplaceAccessChangesDto? AccessChanges { get; set; } } [DataContract] @@ -11602,6 +11720,33 @@ public partial class GetMarketplaceIntegration public virtual string IntegrationViewId { get; set; } } + [NorbixRoute("/{version}/code/marketplace/integrations/{IntegrationViewId}/access", "GET")] + public partial class GetMarketplaceIntegrationAccess + : CodeMashRequestBase, INorbixRequest + { + /// + ///Integration view id, from get_marketplace_integrations. + /// + public virtual string IntegrationViewId { get; set; } + } + + [DataContract] + public partial class GetMarketplaceIntegrationAccessResponse + : ResponseBase + { + [DataMember] + public virtual IReadOnlyList Roles { get; set; } + + [DataMember] + public virtual IReadOnlyList Packages { get; set; } + + [DataMember] + public virtual IReadOnlyList Tools { get; set; } + + [DataMember] + public virtual IReadOnlyList Functions { get; set; } + } + public partial class GetMarketplaceIntegrationResponse : ResponseBase { @@ -11687,6 +11832,98 @@ public partial class SaveMarketplaceIntegration public virtual Dictionary Secrets { get; set; } = new(); } + [NorbixRoute("/{version}/code/marketplace/integrations/{IntegrationViewId}/access/roles/custom", "POST")] + [DataContract] + public partial class SaveMarketplaceIntegrationCustomAccess + : CodeMashRequestBase, INorbixRequest + { + /// + ///Integration view id, from get_marketplace_integrations. + /// + [DataMember] + public virtual string IntegrationViewId { get; set; } + + /// + ///The role's composite id: acc_…_nr_… or pr_…_nr_…. + /// + [DataMember] + public virtual string RoleId { get; set; } + + /// + ///The package to start from: None, Viewer, FunctionsOnly, ReadOnly or FullAccess. + /// + [DataMember] + public virtual MarketplaceAccessLevel BasePackage { get; set; } + + /// + ///Tool names the role may run (from the connection's tools). + /// + [DataMember] + public virtual List Tools { get; set; } = []; + + /// + ///Function ids (func_…) of this connection the role may run. + /// + [DataMember] + public virtual List Functions { get; set; } = []; + + /// + ///True to include the Manage rights (edit the connection, its secrets, validation and functions). + /// + [DataMember] + public virtual bool Manage { get; set; } + + /// + ///True to write a new policy for this role only when its current custom policy is shared with other roles. + /// + [DataMember] + public virtual bool NewPolicyForThisRoleOnly { get; set; } + } + + [DataContract] + public partial class SaveMarketplaceIntegrationCustomAccessResponse + : ResponseBase + { + [DataMember] + public virtual string? PolicyId { get; set; } + + [DataMember] + public virtual bool Created { get; set; } + + [DataMember] + public virtual int UsedByRoles { get; set; } + } + + [NorbixRoute("/{version}/code/marketplace/integrations/{IntegrationViewId}/access/roles", "PUT")] + [DataContract] + public partial class SetMarketplaceIntegrationRoleAccess + : CodeMashRequestBase, INorbixRequest + { + /// + ///Integration view id, from get_marketplace_integrations. + /// + [DataMember] + public virtual string IntegrationViewId { get; set; } + + /// + ///The role's composite id: acc_…_nr_… (account / team role) or pr_…_nr_… (project role). + /// + [DataMember] + public virtual string RoleId { get; set; } + + /// + ///None, Viewer, FunctionsOnly, ReadOnly or FullAccess. + /// + [DataMember] + public virtual MarketplaceAccessLevel Package { get; set; } + + /// + ///True to also give the Manage package (edit the connection, its secrets, validation and functions). + /// + [DataMember] + public virtual bool Manage { get; set; } + } + [NorbixRoute("/{version}/code/marketplace/integrations/{IntegrationViewId}/token-mappings", "PUT")] [DataContract] public partial class SetMarketplaceIntegrationTokenMappingsRequest From cdba5792854bb4dda78f810c94d1913e22b5784e Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 16:02:33 +0300 Subject: [PATCH 2/4] =?UTF-8?q?feat(marketplace):=20connection=20access=20?= =?UTF-8?q?matrix,=20role=20package=20and=20custom=20policy=20(routes=2025?= =?UTF-8?q?=E2=80=9327)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The source generator adds GetMarketplaceIntegrationAccessAsync, SetMarketplaceIntegrationRoleAccessAsync and SaveMarketplaceIntegrationCustomAccessAsync from the regenerated Hub types. MarketplaceAccessTests pins each one with a Verify snapshot (the id in the path, no body on the GET, the role / ticks in the PUT / POST body) and reads AccessChanges and the step ErrorCode from the Test response. EndpointCoverageTests.Hub.Code accepted: Count 39 -> 42, the three route entries and AccessChanges. README section. Co-Authored-By: Claude Opus 5.5 --- README.md | 25 +++ .../MarketplaceAccessTests.cs | 140 +++++++++++++++++ ...ndpointCoverageTests.Hub.Code.verified.txt | 146 +++++++++++++++++- ...n_id_in_the_path_with_no_body.verified.txt | 45 ++++++ ...st_with_the_ticks_in_the_body.verified.txt | 27 ++++ ...put_with_the_role_in_the_body.verified.txt | 18 +++ ...anges_and_the_step_error_code.verified.txt | 21 +++ 7 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 tests/Norbix.Hub.Tests/MarketplaceAccessTests.cs create mode 100644 tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Access_matrix_puts_the_connection_id_in_the_path_with_no_body.verified.txt create mode 100644 tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Custom_policy_is_a_post_with_the_ticks_in_the_body.verified.txt create mode 100644 tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Role_package_is_a_put_with_the_role_in_the_body.verified.txt create mode 100644 tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Test_response_reads_access_changes_and_the_step_error_code.verified.txt diff --git a/README.md b/README.md index 6786ea9..b62b7da 100644 --- a/README.md +++ b/README.md @@ -1048,6 +1048,31 @@ foreach (var tool in catalog?.Tools ?? []) Console.WriteLine($"{tool.Name} {(tool.ReadOnlyHint == true ? "read-only" : "writes")}"); ``` +## Marketplace connections — access + +Who may use a connection is set per role with generated access packages +(`MarketplaceAccessLevel`: `None`, `Viewer`, `FunctionsOnly`, `ReadOnly`, +`FullAccess`, plus the `Manage` flag): + +| Method | Call | +| --- | --- | +| `Code.GetMarketplaceIntegrationAccessAsync` | `GET /{version}/code/marketplace/integrations/{IntegrationViewId}/access` — the matrix: `Roles` (package, `IsCustom`, `Manage`, `Custom` policies, `DefaultNotApplied`), `Packages`, `Tools` (`ReadOnly`, `Grantable`), `Functions` | +| `Code.SetMarketplaceIntegrationRoleAccessAsync` | `PUT …/access/roles` — `RoleId`, `Package`, `Manage`; returns the role id | +| `Code.SaveMarketplaceIntegrationCustomAccessAsync` | `POST …/access/roles/custom` — `RoleId`, `BasePackage`, `Tools`, `Functions`, `Manage`, `NewPolicyForThisRoleOnly`; returns `PolicyId`, `Created`, `UsedByRoles` | + +`TestMarketplaceIntegrationAsync` also returns `AccessChanges` +(`ReadOnlyAdded`, `ReadOnlyRemoved`, `NotGrantable`) and each step's +`ErrorCode`. + +```csharp +await hub.Code.SetMarketplaceIntegrationRoleAccessAsync(new SetMarketplaceIntegrationRoleAccess +{ + IntegrationViewId = "int_…", + RoleId = "acc_…_nr_…", + Package = MarketplaceAccessLevel.ReadOnly, +}); +``` + ## Coverage Notes Generated coverage tracks the API and Hub DTO contract files. Some flows are not generated automatically: diff --git a/tests/Norbix.Hub.Tests/MarketplaceAccessTests.cs b/tests/Norbix.Hub.Tests/MarketplaceAccessTests.cs new file mode 100644 index 0000000..ca95c56 --- /dev/null +++ b/tests/Norbix.Hub.Tests/MarketplaceAccessTests.cs @@ -0,0 +1,140 @@ +using Norbix.Sdk.Tests.Helpers; +using Norbix.Sdk.Types.Hub; + +using NUnit.Framework; + +using VerifyNUnit; + +namespace Norbix.Hub.Tests; + +/// +/// Routes 25–27 of the marketplace contract (gateway item access): the access +/// matrix of a connection, a role's package and a custom access policy. The +/// coverage snapshot sends placeholder ids; these cases pin a real connection id +/// in the path, no body on the GET, the role and the ticks in the PUT / POST +/// body, and the answers read back from the generated responses. +/// Nothing leaves the process. +/// +[TestFixture] +public sealed class MarketplaceAccessTests +{ + private const string Integration = "int_6Qp1dW2kRm8NsT4vYc0Hb"; + private const string Role = "acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU"; + private static readonly string[] ReadOnlyAdded = ["get_me"]; + private static readonly string[] NotGrantable = ["bad name"]; + + private static NorbixTestFixture NewFixture() => + NorbixTestFixture.Create(o => + { + o.AccountId = "test-account"; + o.BearerToken = "test-bearer"; + }); + + [Test] + public async Task Access_matrix_puts_the_connection_id_in_the_path_with_no_body() + { + using var fixture = NewFixture(); + fixture.RespondNext(new + { + roles = new[] + { + new + { + roleId = Role, + name = "Projects Developer", + scope = "account", + isSystem = true, + @fixed = false, + package = "None", + isCustom = false, + manage = false, + custom = Array.Empty(), + defaultNotApplied = "The person who enabled the connection may not manage policies.", + }, + }, + packages = new[] { new { kind = "ReadOnly", policyId = "pol_1", name = "GitHub — Read-only", exists = true } }, + tools = new[] { new { name = "get_me", readOnly = true, grantable = true } }, + functions = new[] { new { functionId = "func_1", name = "Create issue", functionKey = "create_issue" } }, + }); + + var response = await fixture.Client.Code.GetMarketplaceIntegrationAccessAsync( + new GetMarketplaceIntegrationAccess { IntegrationViewId = Integration }); + + await Verifier.Verify( + new { Sent = fixture.LastRequest, response!.Roles, response.Packages, response.Tools, response.Functions }, + VerifyConfig.VerifySettings); + } + + [Test] + public async Task Role_package_is_a_put_with_the_role_in_the_body() + { + using var fixture = NewFixture(); + fixture.RespondNext(new { id = Role }); + + var response = await fixture.Client.Code.SetMarketplaceIntegrationRoleAccessAsync( + new SetMarketplaceIntegrationRoleAccess + { + IntegrationViewId = Integration, + RoleId = Role, + Package = MarketplaceAccessLevel.ReadOnly, + Manage = false, + }); + + await Verifier.Verify( + new { Sent = fixture.LastRequest, response!.Id }, + VerifyConfig.VerifySettings); + } + + [Test] + public async Task Custom_policy_is_a_post_with_the_ticks_in_the_body() + { + using var fixture = NewFixture(); + fixture.RespondNext(new { policyId = "pol_9", created = true, usedByRoles = 1 }); + + var response = await fixture.Client.Code.SaveMarketplaceIntegrationCustomAccessAsync( + new SaveMarketplaceIntegrationCustomAccess + { + IntegrationViewId = Integration, + RoleId = Role, + BasePackage = MarketplaceAccessLevel.ReadOnly, + Tools = ["create_issue"], + Functions = ["func_4hT2b9QkLm3XcT1yRfGh7a"], + Manage = false, + NewPolicyForThisRoleOnly = true, + }); + + await Verifier.Verify( + new { Sent = fixture.LastRequest, response!.PolicyId, response.Created, response.UsedByRoles }, + VerifyConfig.VerifySettings); + } + + [Test] + public async Task Test_response_reads_access_changes_and_the_step_error_code() + { + using var fixture = NewFixture(); + fixture.RespondNext(new + { + steps = new[] + { + new + { + key = "mcp.tools", + label = "Tools listed", + requiredForFunctions = true, + succeeded = false, + skipped = false, + message = "The server listed no tools.", + errorCode = "CM-ERRORS-MARKETPLACE-018", + }, + }, + accessChanges = new { readOnlyAdded = ReadOnlyAdded, readOnlyRemoved = Array.Empty(), notGrantable = NotGrantable }, + }); + + var response = await fixture.Client.Code.TestMarketplaceIntegrationAsync( + new TestMarketplaceIntegration { IntegrationViewId = Integration }); + + await Verifier.Verify( + new { response!.Steps, response.AccessChanges }, + VerifyConfig.VerifySettings); + } +} 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 1a9d872..14d4f2e 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 @@ -1,7 +1,7 @@ { Target: Hub, Group: code, - Count: 39, + Count: 42, Endpoints: [ { Target: Hub, @@ -1022,6 +1022,57 @@ } } }, + { + Target: Hub, + Group: code, + MethodName: GetMarketplaceIntegrationAccessAsync, + RequestType: GetMarketplaceIntegrationAccess, + HttpMethod: GET, + Path: /{version}/code/marketplace/integrations/{IntegrationViewId}/access, + PathParams: [ + IntegrationViewId + ], + IsAccountScoped: false, + IsUnauthenticated: false, + Sent: { + Method: GET, + Path: /v2/code/marketplace/integrations/{guid}/access, + Query: ?env=test-env, + Headers: { + Authorization: Bearer test-bearer, + norbix-account-id: test-account, + norbix-project-id: test-project + } + }, + ResponseType: GetMarketplaceIntegrationAccessResponse, + Response: { + Roles: [ + { + IsSystem: false, + Fixed: false, + IsCustom: false, + Manage: false + } + ], + Packages: [ + { + Exists: false + } + ], + Tools: [ + { + ReadOnly: false, + Grantable: false + } + ], + Functions: [ + {} + ], + ResponseStatus: { + IsSuccess: true + } + } + }, { Target: Hub, Group: code, @@ -1215,6 +1266,88 @@ } } }, + { + Target: Hub, + Group: code, + MethodName: SaveMarketplaceIntegrationCustomAccessAsync, + RequestType: SaveMarketplaceIntegrationCustomAccess, + HttpMethod: POST, + Path: /{version}/code/marketplace/integrations/{IntegrationViewId}/access/roles/custom, + PathParams: [ + IntegrationViewId + ], + IsAccountScoped: false, + IsUnauthenticated: false, + Sent: { + Method: POST, + Path: /v2/code/marketplace/integrations/{guid}/access/roles/custom, + Headers: { + Authorization: Bearer test-bearer, + Content-Type: application/json; charset=utf-8, + norbix-account-id: test-account, + norbix-project-id: test-project + }, + Body: { + roleId: Guid_1, + basePackage: none, + tools: [ + test-string + ], + functions: [ + test-string + ], + manage: true, + newPolicyForThisRoleOnly: true, + env: test-env + } + }, + ResponseType: SaveMarketplaceIntegrationCustomAccessResponse, + Response: { + PolicyId: Guid_1, + Created: true, + UsedByRoles: 7, + ResponseStatus: { + IsSuccess: true + } + } + }, + { + Target: Hub, + Group: code, + MethodName: SetMarketplaceIntegrationRoleAccessAsync, + RequestType: SetMarketplaceIntegrationRoleAccess, + HttpMethod: PUT, + Path: /{version}/code/marketplace/integrations/{IntegrationViewId}/access/roles, + PathParams: [ + IntegrationViewId + ], + IsAccountScoped: false, + IsUnauthenticated: false, + Sent: { + Method: PUT, + Path: /v2/code/marketplace/integrations/{guid}/access/roles, + Headers: { + Authorization: Bearer test-bearer, + Content-Type: application/json; charset=utf-8, + norbix-account-id: test-account, + norbix-project-id: test-project + }, + Body: { + roleId: Guid_1, + package: none, + manage: true, + env: test-env + } + }, + ResponseType: IdResponse, + Response: { + Id: Guid_1, + Status: test-status, + ResponseStatus: { + IsSuccess: true + } + } + }, { Target: Hub, Group: code, @@ -1287,6 +1420,17 @@ Skipped: false } ], + AccessChanges: { + ReadOnlyAdded: [ + test-string + ], + ReadOnlyRemoved: [ + test-string + ], + NotGrantable: [ + test-string + ] + }, ResponseStatus: { IsSuccess: true } diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Access_matrix_puts_the_connection_id_in_the_path_with_no_body.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Access_matrix_puts_the_connection_id_in_the_path_with_no_body.verified.txt new file mode 100644 index 0000000..ef58809 --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Access_matrix_puts_the_connection_id_in_the_path_with_no_body.verified.txt @@ -0,0 +1,45 @@ +{ + Sent: { + Method: GET, + Path: /v2/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/access, + Headers: { + Authorization: Bearer test-bearer, + norbix-account-id: test-account, + norbix-project-id: test-project + } + }, + Roles: [ + { + RoleId: acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU, + Name: Projects Developer, + Scope: account, + IsSystem: true, + Fixed: false, + IsCustom: false, + Manage: false, + DefaultNotApplied: The person who enabled the connection may not manage policies. + } + ], + Packages: [ + { + Kind: ReadOnly, + PolicyId: pol_1, + Name: GitHub — Read-only, + Exists: true + } + ], + Tools: [ + { + Name: get_me, + ReadOnly: true, + Grantable: true + } + ], + Functions: [ + { + FunctionId: func_1, + Name: Create issue, + FunctionKey: create_issue + } + ] +} \ No newline at end of file diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Custom_policy_is_a_post_with_the_ticks_in_the_body.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Custom_policy_is_a_post_with_the_ticks_in_the_body.verified.txt new file mode 100644 index 0000000..30f1a66 --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Custom_policy_is_a_post_with_the_ticks_in_the_body.verified.txt @@ -0,0 +1,27 @@ +{ + Sent: { + Method: POST, + Path: /v2/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/access/roles/custom, + Headers: { + Authorization: Bearer test-bearer, + Content-Type: application/json; charset=utf-8, + norbix-account-id: test-account, + norbix-project-id: test-project + }, + Body: { + roleId: acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU, + basePackage: readOnly, + tools: [ + create_issue + ], + functions: [ + func_4hT2b9QkLm3XcT1yRfGh7a + ], + manage: false, + newPolicyForThisRoleOnly: true + } + }, + PolicyId: pol_9, + Created: true, + UsedByRoles: 1 +} \ No newline at end of file diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Role_package_is_a_put_with_the_role_in_the_body.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Role_package_is_a_put_with_the_role_in_the_body.verified.txt new file mode 100644 index 0000000..66246cc --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Role_package_is_a_put_with_the_role_in_the_body.verified.txt @@ -0,0 +1,18 @@ +{ + Sent: { + Method: PUT, + Path: /v2/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/access/roles, + Headers: { + Authorization: Bearer test-bearer, + Content-Type: application/json; charset=utf-8, + norbix-account-id: test-account, + norbix-project-id: test-project + }, + Body: { + roleId: acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU, + package: readOnly, + manage: false + } + }, + Id: acc_6Qp1dW2kRm8NsT4vYc0Hb_nr_3kLm9QwErTyU +} \ No newline at end of file diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Test_response_reads_access_changes_and_the_step_error_code.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Test_response_reads_access_changes_and_the_step_error_code.verified.txt new file mode 100644 index 0000000..ccf1eca --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceAccessTests.Test_response_reads_access_changes_and_the_step_error_code.verified.txt @@ -0,0 +1,21 @@ +{ + Steps: [ + { + Key: mcp.tools, + Label: Tools listed, + RequiredForFunctions: true, + Succeeded: false, + Skipped: false, + Message: The server listed no tools., + ErrorCode: CM-ERRORS-MARKETPLACE-018 + } + ], + AccessChanges: { + ReadOnlyAdded: [ + get_me + ], + NotGrantable: [ + bad name + ] + } +} \ No newline at end of file From 97e78862e0c772a5c5ee964e024faa73af4d5115 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 16:23:50 +0300 Subject: [PATCH 3/4] chore(types): regenerate hub types from gateway refactoringV2 515275038 (oauth, route 28) Adds StartMarketplaceIntegrationSignIn / StartMarketplaceIntegrationSignInResponse (authorizeUrl, redirectUrl, expiresAt) and the additive fields MarketplaceIntegrationDto.signedIn / signedInBy / signedInAt / tokenExpiresAt, MarketplaceIntegrationListProjection.signedIn / signedInAt and MarketplaceListingDto.connectSteps / oauthRedirectUrl. Route 29 (the OAuth callback) is not in the type metadata. Co-Authored-By: Claude Opus 5.5 --- src/Norbix.Sdk.Types/Generated/Hub.dtos.cs | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs index 2188a42..843286c 100644 --- a/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs +++ b/src/Norbix.Sdk.Types/Generated/Hub.dtos.cs @@ -4094,6 +4094,18 @@ public partial class MarketplaceIntegrationDto [DataMember] public virtual List? LastValidationSteps { get; set; } + + [DataMember] + public virtual bool SignedIn { get; set; } + + [DataMember] + public virtual string? SignedInBy { get; set; } + + [DataMember] + public virtual DateTime? SignedInAt { get; set; } + + [DataMember] + public virtual DateTime? TokenExpiresAt { get; set; } } [DataContract] @@ -4135,6 +4147,12 @@ public partial class MarketplaceIntegrationListProjection [DataMember] public virtual bool RequiresHumanDeliveryConfirmation { get; set; } + + [DataMember] + public virtual bool SignedIn { get; set; } + + [DataMember] + public virtual DateTime? SignedInAt { get; set; } } [DataContract] @@ -4174,6 +4192,12 @@ public partial class MarketplaceListingDto [DataMember] public virtual MarketplaceAvailability Availability { get; set; } + [DataMember] + public virtual IReadOnlyList ConnectSteps { get; set; } + + [DataMember] + public virtual string? OAuthRedirectUrl { get; set; } + [DataMember] public virtual bool IsOfficial { get; set; } @@ -11944,6 +11968,24 @@ public partial class SetMarketplaceIntegrationTokenMappingsResponse { } + [NorbixRoute("/{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/start", "POST")] + public partial class StartMarketplaceIntegrationSignIn + : CodeMashRequestBase, INorbixRequest + { + /// + ///Integration view id, from get_marketplace_integrations. + /// + public virtual string IntegrationViewId { get; set; } + } + + public partial class StartMarketplaceIntegrationSignInResponse + : ResponseBase + { + public virtual string? AuthorizeUrl { get; set; } + public virtual string? RedirectUrl { get; set; } + public virtual DateTime? ExpiresAt { get; set; } + } + [NorbixRoute("/{version}/code/marketplace/integrations/{IntegrationViewId}/test", "POST")] public partial class TestMarketplaceIntegration : CodeMashRequestBase, INorbixRequest From 3d012867e9d0ba4130fd0ec6db1eda33b373f0c2 Mon Sep 17 00:00:00 2001 From: CodeMash Date: Tue, 13 Oct 2026 16:26:07 +0300 Subject: [PATCH 4/4] feat(marketplace): StartMarketplaceIntegrationSignInAsync, the OAuth sign-in start (route 28) The source generator adds StartMarketplaceIntegrationSignInAsync (POST .../integrations/{IntegrationViewId}/oauth/start) from the regenerated Hub types. MarketplaceOAuthTests pins the id in the path and reads AuthorizeUrl / RedirectUrl / ExpiresAt. EndpointCoverageTests.Hub.Code accepted: Count 42 -> 43, the route entry, SignedIn on the integration DTOs (the stub fills the first 8 properties, so SignedIn takes the place of ViewId in the SaveMarketplaceIntegration body). README note. Route 29 (the callback) is not in the type metadata and has no method. Co-Authored-By: Claude Opus 5.5 --- README.md | 7 +++ .../Norbix.Hub.Tests/MarketplaceOAuthTests.cs | 42 +++++++++++++++++ ...ndpointCoverageTests.Hub.Code.verified.txt | 45 +++++++++++++++++-- ...and_returns_the_authorize_url.verified.txt | 14 ++++++ 4 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 tests/Norbix.Hub.Tests/MarketplaceOAuthTests.cs create mode 100644 tests/Norbix.Hub.Tests/test_results/MarketplaceOAuthTests.Sign_in_start_puts_the_connection_id_in_the_path_and_returns_the_authorize_url.verified.txt diff --git a/README.md b/README.md index b62b7da..6c61bda 100644 --- a/README.md +++ b/README.md @@ -1060,6 +1060,13 @@ Who may use a connection is set per role with generated access packages | `Code.SetMarketplaceIntegrationRoleAccessAsync` | `PUT …/access/roles` — `RoleId`, `Package`, `Manage`; returns the role id | | `Code.SaveMarketplaceIntegrationCustomAccessAsync` | `POST …/access/roles/custom` — `RoleId`, `BasePackage`, `Tools`, `Functions`, `Manage`, `NewPolicyForThisRoleOnly`; returns `PolicyId`, `Created`, `UsedByRoles` | +`Code.StartMarketplaceIntegrationSignInAsync` +(`POST /{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/start`) +starts the OAuth sign-in of an MCP connection: open `AuthorizeUrl` in a browser +(`RedirectUrl` is the callback the vendor app must allow, `ExpiresAt` ends the +attempt). A signed-in connection shows `SignedIn`, `SignedInBy`, `SignedInAt` +and `TokenExpiresAt`; a listing shows `ConnectSteps` and `OAuthRedirectUrl`. + `TestMarketplaceIntegrationAsync` also returns `AccessChanges` (`ReadOnlyAdded`, `ReadOnlyRemoved`, `NotGrantable`) and each step's `ErrorCode`. diff --git a/tests/Norbix.Hub.Tests/MarketplaceOAuthTests.cs b/tests/Norbix.Hub.Tests/MarketplaceOAuthTests.cs new file mode 100644 index 0000000..73f7fb4 --- /dev/null +++ b/tests/Norbix.Hub.Tests/MarketplaceOAuthTests.cs @@ -0,0 +1,42 @@ +using Norbix.Sdk.Tests.Helpers; +using Norbix.Sdk.Types.Hub; + +using NUnit.Framework; + +using VerifyNUnit; + +namespace Norbix.Hub.Tests; + +/// +/// Route 28 of the marketplace contract (gateway item oauth): start the OAuth +/// sign-in of an MCP connection. The coverage snapshot sends a placeholder id; +/// this case pins a real connection id in the path and reads the authorize URL +/// back from the generated response. Route 29 (the callback) is not in the type +/// metadata and has no SDK method. Nothing leaves the process. +/// +[TestFixture] +public sealed class MarketplaceOAuthTests +{ + [Test] + public async Task Sign_in_start_puts_the_connection_id_in_the_path_and_returns_the_authorize_url() + { + using var fixture = NorbixTestFixture.Create(o => + { + o.AccountId = "test-account"; + o.BearerToken = "test-bearer"; + }); + fixture.RespondNext(new + { + authorizeUrl = "https://slack.com/oauth/v2/authorize?client_id=1&state=s1", + redirectUrl = "https://hub.norbix.ai/v3/code/marketplace/oauth/callback", + expiresAt = "2026-10-13T08:10:00Z", + }); + + var response = await fixture.Client.Code.StartMarketplaceIntegrationSignInAsync( + new StartMarketplaceIntegrationSignIn { IntegrationViewId = "int_6Qp1dW2kRm8NsT4vYc0Hb" }); + + await Verifier.Verify( + new { Sent = fixture.LastRequest, response!.AuthorizeUrl, response.RedirectUrl, response.ExpiresAt }, + VerifyConfig.VerifySettings); + } +} 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 14d4f2e..878296c 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 @@ -1,7 +1,7 @@ { Target: Hub, Group: code, - Count: 42, + Count: 43, Endpoints: [ { Target: Hub, @@ -1015,7 +1015,8 @@ Succeeded: false, Skipped: false } - ] + ], + SignedIn: true }, ResponseStatus: { IsSuccess: true @@ -1098,7 +1099,8 @@ Items: [ { IsEnabled: false, - RequiresHumanDeliveryConfirmation: false + RequiresHumanDeliveryConfirmation: false, + SignedIn: false } ], HasMore: false, @@ -1247,7 +1249,7 @@ lastValidationSteps: [ null ], - viewId: Guid_1, + signedIn: true, isEnabled: false, requiresHumanDeliveryConfirmation: false }, @@ -1383,6 +1385,41 @@ } } }, + { + Target: Hub, + Group: code, + MethodName: StartMarketplaceIntegrationSignInAsync, + RequestType: StartMarketplaceIntegrationSignIn, + HttpMethod: POST, + Path: /{version}/code/marketplace/integrations/{IntegrationViewId}/oauth/start, + PathParams: [ + IntegrationViewId + ], + IsAccountScoped: false, + IsUnauthenticated: false, + Sent: { + Method: POST, + Path: /v2/code/marketplace/integrations/{guid}/oauth/start, + Headers: { + Authorization: Bearer test-bearer, + Content-Type: application/json; charset=utf-8, + norbix-account-id: test-account, + norbix-project-id: test-project + }, + Body: { + env: test-env + } + }, + ResponseType: StartMarketplaceIntegrationSignInResponse, + Response: { + AuthorizeUrl: https://example.test/authorizeUrl, + RedirectUrl: https://example.test/redirectUrl, + ExpiresAt: DateTime_1, + ResponseStatus: { + IsSuccess: true + } + } + }, { Target: Hub, Group: code, diff --git a/tests/Norbix.Hub.Tests/test_results/MarketplaceOAuthTests.Sign_in_start_puts_the_connection_id_in_the_path_and_returns_the_authorize_url.verified.txt b/tests/Norbix.Hub.Tests/test_results/MarketplaceOAuthTests.Sign_in_start_puts_the_connection_id_in_the_path_and_returns_the_authorize_url.verified.txt new file mode 100644 index 0000000..e7b5a5c --- /dev/null +++ b/tests/Norbix.Hub.Tests/test_results/MarketplaceOAuthTests.Sign_in_start_puts_the_connection_id_in_the_path_and_returns_the_authorize_url.verified.txt @@ -0,0 +1,14 @@ +{ + Sent: { + Method: POST, + Path: /v2/code/marketplace/integrations/int_6Qp1dW2kRm8NsT4vYc0Hb/oauth/start, + Headers: { + Authorization: Bearer test-bearer, + norbix-account-id: test-account, + norbix-project-id: test-project + } + }, + AuthorizeUrl: https://slack.com/oauth/v2/authorize?client_id=1&state=s1, + RedirectUrl: https://hub.norbix.ai/v3/code/marketplace/oauth/callback, + ExpiresAt: DateTime_1 +} \ No newline at end of file