Skip to content

Conversation

Dev1ByteSoftware
Copy link
Contributor

@Dev1ByteSoftware Dev1ByteSoftware commented Sep 11, 2025

Summary by CodeRabbit

  • New Features
    • Introduced .NET SDK with clients for Users, Roles, Events, Issue Categories, and Feature access checks.
    • Added authentication flows and DI registration for easy setup.
    • Included common models and pagination support.
  • Tests
    • Added test project with coverage tooling and integration tests for Users, Roles, Events, and Features.
  • Documentation
    • Updated README with getting started and NuGet install guidance; added CHANGELOG template.
  • Chores
    • Added license, solution setup, and comprehensive .gitignore.

Copy link

coderabbitai bot commented Sep 11, 2025

Walkthrough

Introduces a new .NET 6 SDK and test solution for JFW: solution/project scaffolding, HTTP connection layer, base abstractions, constants, helpers, models, API interfaces and clients (users, roles, features, events, issue categories), authentication API, DI registration, and xUnit tests. Adds repository metadata files (.gitignore, LICENSE, README, CHANGELOG).

Changes

Cohort / File(s) Change summary
Solution & Repo Setup
JFW.Sdk.sln, JFW.Sdk/JFW.Sdk.csproj, JFW.Sdk.Tests/JFW.Sdk.Tests.csproj, .gitignore, LICENSE, README.md, CHANGELOG.md
Adds solution with SDK and tests, project configs, extensive gitignore, Apache-2.0 license, updated README, and a skeleton changelog.
Core Abstractions & Helpers
JFW.Sdk/Abstracts/ApiResponse.cs, .../BaseClient.cs, .../BaseJfwClient.cs, .../BaseModel.cs, .../HttpRequest.cs, .../JfwException.cs, .../PaginationList.cs, JFW.Sdk/Helpers/UrlHelper.cs, JFW.Sdk/GlobalUsing.cs
Introduces generic API response/error types, base HTTP clients, base model, request builder, SDK exception (code-based), pagination container, URL construction helpers, and global usings.
HTTP Connection Layer
JFW.Sdk/IManagementConnection.cs, JFW.Sdk/ManagementConnection.cs
Defines and implements a typed HTTP connection with generic verb methods, JSON handling, header support, ApiResponse unwrapping, and exception translation.
Constants
JFW.Sdk/Constants/HeaderKeys.cs, .../HostUrl.cs, .../JfwStatus.cs, .../UserTypes.cs
Adds constants for headers and host, a status enum, and user type string constants.
Authentication API
JFW.Sdk/Apis/Interfaces/IAuthApi.cs, JFW.Sdk/Apis/Implements/AuthApi.cs
Adds auth interface and implementation for login, passwordless flows, registration, and forgot password using BaseJFWClient with logging and exception wrapping.
Domain Clients - Interfaces
JFW.Sdk/Clients/Interfaces/IEventClient.cs, .../IFeaturesClient.cs, .../IIssueCategoriesClient.cs, .../IRolesClient.cs, .../IUsersClient.cs
Declares contracts for events, features, issue categories, roles, and users operations.
Domain Clients - Implementations
JFW.Sdk/Clients/Implements/EventClient.cs, .../FeaturesClient.cs, .../IssueCategoriesClient.cs, .../RolesClient.cs, .../UsersClient.cs
Implements clients over BaseClient using IManagementConnection for GET/POST endpoints, with URI/query building and nullable returns.
API Aggregator
JFW.Sdk/IJfwApiClient.cs, JFW.Sdk/JfwApiClient.cs
Adds aggregator exposing domain clients, default header management, and auth key updates across clients.
Models - Requests/Responses
JFW.Sdk/Models/Requests/AuthenticationRequest.cs, .../Requests/README.md, JFW.Sdk/Models/Responses/BaseResponse.cs, .../Responses/AuthenticationResponse.cs
Adds auth request/response DTOs and a base response type; README explains request models directory.
Models - Pagination & Base
JFW.Sdk/Models/BasePagination.cs
Adds base pagination with defaults.
Models - Events
JFW.Sdk/Models/Events/Event.cs, .../Events/GetEventsRequest.cs
Adds Event model and filterable GetEventsRequest with ToDictionary().
Models - Issue Categories
JFW.Sdk/Models/IssueCategories/IssueCategory.cs
Adds IssueCategory model.
Models - Roles
JFW.Sdk/Models/Roles/Role.cs
Adds Role model with permissions.
Models - Users
JFW.Sdk/Models/Users/User.cs, .../Users/UserBaseCreateRequest.cs, .../Users/UserCreateByEmailRequest.cs, .../Users/UserCreateByPhoneRequest.cs, .../Users/GetUsersRequest.cs
Adds User model and create/get request DTOs, including ToDictionary() for list queries.
Dependency Injection
JFW.Sdk/Extensions/ServiceCollectionExtensions.cs
Adds IServiceCollection extension to configure options, typed HttpClient, and register Auth API.
Tests - Setup & Usings
JFW.Sdk.Tests/Usings.cs
Adds global xUnit using.
Tests - Users/Features/Roles/Events
JFW.Sdk.Tests/UsersTest.cs, .../FeaturesTests.cs, .../RolesTests.cs, .../EventsTests.cs
Adds xUnit tests invoking SDK clients via ManagementConnection; asserts types/behaviors using placeholder values.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Test/Caller
  participant Api as IJfwApiClient
  participant Users as UsersClient
  participant Conn as ManagementConnection
  participant HTTP as Remote API

  Dev->>Api: UpdateAuthKey(key)
  Dev->>Api: Users.GetByIdAsync(userId)
  Api->>Users: Forward call
  Users->>Conn: GET api/v1/users/{id}\n+ DefaultHeaders
  Conn->>HTTP: HTTP GET (headers, query)
  HTTP-->>Conn: ApiResponse<User>
  alt Success = true
    Conn-->>Users: User
    Users-->>Api: User
    Api-->>Dev: User
  else Errors present
    Conn--x Users: throws JfwException
    Users--x Api: propagate
    Api--x Dev: JfwException
  end
Loading
sequenceDiagram
  autonumber
  actor Dev as App
  participant DI as IServiceCollection
  participant BJFW as BaseJFWClient
  participant Auth as AuthApi
  participant HTTP as Remote API

  Dev->>DI: AddJfwSdk(options)
  DI-->>Dev: Services configured

  Dev->>Auth: AuthAsync(request)
  Auth->>BJFW: PostAsync<AuthenticationResponse>(HttpRequest{uri,payload})
  BJFW->>HTTP: POST /api/v1/users/auth\nBrand-Url, Auth-Key
  HTTP-->>BJFW: 200 + body or error
  BJFW-->>Auth: AuthenticationResponse
  Auth-->>Dev: AuthenticationResponse
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks (1 passed, 1 warning, 1 inconclusive)

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.99% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "Dev" is too short and generic to convey the primary change; it does not summarize the large, cross-cutting additions in this PR. The changeset appears to add an initial JFW .NET SDK surface (clients, models, ManagementConnection, JfwApiClient, DI extension, tests, and supporting files), so the title should state the main intent for reviewers and history. Please rename the PR to a concise, descriptive title that reflects the primary change, for example: "Initial JFW .NET SDK — add clients, models, ManagementConnection and tests" or "Add JfwApiClient, ManagementConnection, domain clients, models and test project". Also update the PR description to call out the single main focus if you want a narrower scope highlighted.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Poem

I thump my paws, the routes align,
New clients hop along the line.
Headers primed, requests take flight—
Burrows of models, typed just right.
Tests nibble paths both true and wrong,
Our garden grows, connections strong.
Hop, ship, merge—onward, long! 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 42

🧹 Nitpick comments (76)
JFW.Sdk/Constants/UserTypes.cs (3)

13-14: Make the constants container static to prevent instantiation/inheritance.

This is a pure constants holder; mark it static.

-    public class UserTypes
+    public static class UserTypes

10-12: Tighten XML docs (short, consistent, non-redundant).

Current phrasing is repetitive (“user user type”) and wordy.

-    /// <summary>
-    /// Provides all the user types.
-    /// </summary>
+    /// <summary>
+    /// Compile-time constants for JFW user role names.
+    /// </summary>

-        /// <summary>
-        /// The super admin user type. This user type is used for the super administrators.
-        /// </summary>
+        /// <summary>
+        /// Super administrator role.
+        /// </summary>

-        /// <summary>
-        /// The admin user type. This user type is used for the administrators.
-        /// </summary>
+        /// <summary>
+        /// Administrator role.
+        /// </summary>

-        /// <summary>
-        /// The partner user type. That user type is used for the partners.
-        /// </summary>
+        /// <summary>
+        /// Partner role.
+        /// </summary>

-        /// <summary>
-        /// The user user type. This user type is used for the users.
-        /// </summary>
+        /// <summary>
+        /// Standard user role.
+        /// </summary>

Also applies to: 15-17, 20-22, 25-27, 30-32


1-6: Drop author/history header; rely on Git.

Keeps files clean and avoids drift with commit history.

-/*
-* Description: This class provides all constants for the user types.
-* Author: Steve Bang.
-* History:
-* - 2024-08-12: Created - Steve Bang.
-*/
JFW.Sdk/Models/Requests/README.md (1)

1-3: Add links and a minimal example for faster onboarding.
Consider listing key request models (e.g., AuthenticationRequest) and a 5–10 line example showing serialization/usage. This keeps the README actionable.

CHANGELOG.md (1)

2-5: Adopt Keep a Changelog + set a real version tag.
Replace placeholders with an Unreleased section and an initial version (e.g., 0.1.0) with today’s date to match this PR’s first drop.

Apply this diff:

-Running changelog of releases since `version`
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](https://semver.org/)
+and follows the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
 
-## A.B.C.D
+## [Unreleased]
+
+## [0.1.0] - 2025-09-11
 ### Added
JFW.Sdk/GlobalUsing.cs (1)

1-3: Nit: rename file to GlobalUsings.cs and keep scope minimal.
Align with SDK template naming and revisit if any of these introduce ambiguity with similarly named types.

JFW.Sdk/Models/Responses/BaseResponse.cs (3)

1-6: Fix incorrect header comment (copy/paste).
Header says “constants for the user types” but this is a response base type. Update to avoid confusion.

Apply this diff:

-/*
-* Description: This class provides all constants for the user types.
+/*
+* Description: Base type for SDK response models.
 * Author: Steve Bang.
 * History:
-* - 2024-08-31: Created - Steve Bang.
+* - 2024-08-31: Created - Steve Bang.
+* - 2025-09-11: Updated description - PR#13.
 */

13-15: Optional: centralize common response metadata.
If many responses share fields (e.g., RequestId, TraceId, Errors), put them here to reduce duplication.

Apply this diff:

     public abstract class BaseResponse
     {
+        /// <summary>Server-generated correlation identifier (if available).</summary>
+        public string? RequestId { get; init; }
+
+        /// <summary>UTC timestamp of the response (if provided by the API).</summary>
+        public DateTimeOffset? Timestamp { get; init; }
+
+        /// <summary>Collection of non-fatal warnings or errors.</summary>
+        public IReadOnlyList<string>? Errors { get; init; }
     }

1-1: Remove UTF-8 BOM.
The file starts with a BOM; standardize on UTF-8 without BOM per typical .NET repos to reduce noisy diffs.

JFW.Sdk/Constants/HostUrl.cs (1)

4-7: Prefer configuration over hard-coded host.
Move the base URL to JfwOptions (with env/override support) and set HttpClient.BaseAddress from options. Keep this constant only as a safe default.

JFW.Sdk/JFW.Sdk.csproj (1)

3-8: Add NuGet package metadata.
Helps publishing and discoverability.

Apply this diff:

   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageId>JFW.Sdk</PackageId>
+    <Title>Jframework .NET SDK</Title>
+    <Description>Official .NET SDK for Jframework APIs.</Description>
+    <Authors>1Byte Software</Authors>
+    <Company>1Byte Software</Company>
+    <RepositoryUrl>https://github.com/1Byte-Software/JFW-NET-Sdk</RepositoryUrl>
+    <RepositoryType>git</RepositoryType>
+    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
+    <PackageTags>JFW;SDK;Jframework;Client;HTTP</PackageTags>
   </PropertyGroup>
LICENSE (1)

1-12: Minor license header polish.
Consider standard Apache-2.0 header formatting (e.g., “Copyright © 2020–Present 1Byte Software”).

JFW.Sdk/Abstracts/BaseModel.cs (1)

8-13: Make the base type abstract (and confirm namespace).

If this is intended only for inheritance, mark it abstract to avoid accidental instantiation. Also, confirm the namespace “JFW.Sdk.Clients.Abstracts” is intentional for a model base (path suggests Abstracts, not Clients).

-public class BaseModel
+public abstract class BaseModel
 {
     /// <summary>
     /// The unique identifier of the model.
     /// </summary>
     public string? Id { get; set; }
 }
JFW.Sdk/Constants/HeaderKeys.cs (1)

8-20: Make the holder static.

Prevent instantiation by marking the class static.

-public class HeaderKeys
+public static class HeaderKeys
 {
     /// <summary>
     /// Authentication key header.
     /// </summary>
     public const string AuthKey = "Auth-Key";
JFW.Sdk/Models/Requests/AuthenticationRequest.cs (1)

13-24: Provide a minimal constructor for required fields.

Avoid relying on null-forgiving for required properties.

 public class AuthenticationRequest
 {
+    public AuthenticationRequest(string username, string password)
+    {
+        Username = username ?? throw new ArgumentNullException(nameof(username));
+        Password = password ?? throw new ArgumentNullException(nameof(password));
+    }
+
     /// <summary>
     /// The username of the user.
     /// </summary>
     public string Username { get; set; } = null!;
JFW.Sdk/Models/BasePagination.cs (1)

12-27: Confirm page numbering semantics and consider simple validation.

Defaulting PageNumber to 0 assumes 0-based paging; many APIs are 1-based. Verify server expectation. Optionally add a Validate() to guard negative page numbers and non-positive sizes.

 public class BasePagination
 {
+    public void Validate()
+    {
+        if (PageNumber < 0) throw new ArgumentOutOfRangeException(nameof(PageNumber), "PageNumber cannot be negative.");
+        if (PageSize <= 0) throw new ArgumentOutOfRangeException(nameof(PageSize), "PageSize must be greater than 0.");
+    }
JFW.Sdk/Models/Users/UserBaseCreateRequest.cs (1)

8-33: Lightweight client-side validation (optional).

If you already use DataAnnotations, add basic constraints to help callers catch issues earlier (lengths and confirm match).

+using System.ComponentModel.DataAnnotations;
+
 public class UserBaseCreateRequest
 {
     /// <summary>
     /// The password for the user to create.
     /// </summary>
-    public string Password { get; set; } = null!;
+    [Required, MinLength(8)]
+    public string Password { get; set; } = null!;
 
     /// <summary>
     /// Confirmation of the password for the user to create.
     /// </summary>
-    public string ConfirmPassword { get; set; } = null!;
+    [Required, Compare(nameof(Password))]
+    public string ConfirmPassword { get; set; } = null!;
 
     /// <summary>
     /// The first name of the user to create.
     /// </summary>
     public string? FirstName { get; set; }
JFW.Sdk/IManagementConnection.cs (3)

27-35: Prefer IReadOnlyDictionary for headers to prevent accidental mutation.

Headers are inputs and shouldn’t be mutated by implementations.

Apply this minimal change (and mirror it on the other methods for consistency):

-        IDictionary<string, string>? headers = null,
+        IReadOnlyDictionary<string, string>? headers = null,

26-27: Avoid silent deserialization fallbacks.

“Returns default if empty or not parsable” risks masking real failures. Consider throwing a typed exception on deserialization errors, or introducing a TrySendAsync/Result alternative to surface parse failures explicitly.


4-4: Verify dependency on Newtonsoft in the public interface surface.

Coupling the transport abstraction to Newtonsoft.Json (JsonConverter[]) reduces flexibility if you later switch to System.Text.Json or custom serializers. Consider hiding serializer details behind an internal adapter or a serializer-agnostic options object.

JFW.Sdk/Constants/JfwStatus.cs (2)

4-7: Tighten summary wording.

Minor grammar/polish.

-/// <summary>
-/// Provide status of the JFW common.
-/// </summary>
+/// <summary>
+/// Provides status for JFW entities.
+/// </summary>

7-18: Consider string-based enum serialization for API clarity.

If the API emits/accepts “Active”/“Inactive”, annotate for string enum JSON to avoid numeric values on the wire.

+ [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
 public enum JfwStatus
 {

Confirm your API actually uses string enum values; otherwise skip.

JFW.Sdk.sln (1)

8-9: Unify SDK-style project type GUIDs (nit).

Optional consistency: use the SDK-style C# GUID for the test project as well.

-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JFW.Sdk.Tests", "JFW.Sdk.Tests\JFW.Sdk.Tests.csproj", "{FE6EB11F-DC22-4A6B-AC71-6C0CC3E97777}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JFW.Sdk.Tests", "JFW.Sdk.Tests\JFW.Sdk.Tests.csproj", "{FE6EB11F-DC22-4A6B-AC71-6C0CC3E97777}"
JFW.Sdk/Clients/Interfaces/IRolesClient.cs (1)

9-18: Pass CancellationToken through client methods.

Expose cancellation to callers for timeouts/shutdowns.

-    /// <param name="id">The id of the role to get.</param>
-    /// <returns></returns>
-    Task<Role?> GetByIdAsync(string id);
+    /// <param name="id">The id of the role to get.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns></returns>
+    Task<Role?> GetByIdAsync(string id, CancellationToken cancellationToken = default);

If you adopt this, remember to thread the token through the implementation and call sites.

JFW.Sdk/Models/Users/User.cs (2)

96-100: Align Status type with shared enum (if API supports it).

Other models use JfwStatus; using string here is inconsistent and can hide invalid values.

-    public string Status { get; set; } = null!;
+    public JfwStatus? Status { get; set; }

If the service returns string values, ensure StringEnumConverter is configured (or keep string).


123-126: Prefer DateTimeOffset for timestamps.

Avoids timezone ambiguity on wire-level dates.

-    public DateTime? ExpiryDate { get; set; }
+    public DateTimeOffset? ExpiryDate { get; set; }
JFW.Sdk/Clients/Interfaces/IIssueCategoriesClient.cs (1)

9-19: Grammar + cancellation support.

Small doc fix and expose CancellationToken.

-    /// Gets a issue category by the given code.
+    /// Gets an issue category by the given code.
@@
-    /// <param name="code">The code of the issue category to get.</param>
-    /// <returns></returns>
-    Task<IssueCategory?> GetByCodeAsync(string code);
+    /// <param name="code">The code of the issue category to get.</param>
+    /// <param name="cancellationToken">The cancellation token.</param>
+    /// <returns></returns>
+    Task<IssueCategory?> GetByCodeAsync(string code, CancellationToken cancellationToken = default);

Propagate the token in the implementation and call sites if adopted.

JFW.Sdk/Abstracts/PaginationList.cs (2)

10-19: Prefer an immutable, materialized collection and guard against invalid totals

IEnumerable is fine, but for DTOs it’s safer to materialize (avoid deferred execution) and make read-only. Also consider preventing negative totals.

-    public IEnumerable<T> Items { get; set; } = Enumerable.Empty<T>();
+    public IReadOnlyList<T> Items { get; init; } = Array.Empty<T>();
@@
-    public int TotalItems { get; set; }
+    public int TotalItems { get; init; }

If other code relies on settable properties, keep set but validate on construction/factory.


4-8: Fill in XML docs

The type and type parameter summaries are blank.

JFW.Sdk/Clients/Implements/FeaturesClient.cs (1)

25-37: Validate inputs and consider CancellationToken

Guard against empty inputs; optionally add a CT if the connection supports it.

-    public Task<bool> CheckUserAccessAsync(string userId, string featureCode)
+    public Task<bool> CheckUserAccessAsync(string userId, string featureCode)
     {
-        return Connection.GetAsync<bool>(
+        if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentException("userId is required.", nameof(userId));
+        if (string.IsNullOrWhiteSpace(featureCode)) throw new ArgumentException("featureCode is required.", nameof(featureCode));
+
+        return Connection.GetAsync<bool>(
             UrlHelper.BuildUri(
                 BaseUri, "check", // api/v1/features/check
                 new Dictionary<string, string>()
                 {
                     { nameof(userId), userId },
                     { nameof(featureCode), featureCode },
                 }), // Build parameters
             DefaultHeaders
             );
     }

If IManagementConnection has CT-aware overloads, propagate it here and through the interface.

JFW.Sdk/Clients/Interfaces/IEventClient.cs (2)

20-30: Expose CancellationToken and return a non-null pagination container

Returning null for lists is awkward for consumers; prefer an empty page. Also surface CT.

-    Task<Event?> GetByIdAsync(string id);
+    Task<Event?> GetByIdAsync(string id, CancellationToken cancellationToken = default);
@@
-    Task<PaginationList<Event>?> GetAllAsync(GetEventsRequest request);
+    Task<PaginationList<Event>> GetAllAsync(GetEventsRequest request, CancellationToken cancellationToken = default);

Confirm implementors and callers are updated.


9-10: File/interface naming consistency

File name is IEventClient.cs but interface is IEventsClient. Align to avoid search/maintenance friction.

JFW.Sdk/Extensions/ServiceCollectionExtensions.cs (1)

41-44: Consider registering the aggregate client and adding transient-fault handling

Register IJfwApiClient (if intended) and add a retry/backoff policy (e.g., Polly) keyed off options.RetryCount/Delay.

JFW.Sdk/Models/Roles/Role.cs (1)

39-43: Expose permissions as read-only for DTO safety

If mutation isn’t required post-deserialization, prefer read-only to avoid accidental modification.

-    public List<string> Permissions { get; set; } = new List<string>();
+    public IReadOnlyList<string> Permissions { get; init; } = Array.Empty<string>();
JFW.Sdk.Tests/FeaturesTests.cs (1)

37-39: Remove redundant type assertion

The result is already bool.

-        Assert.IsType<bool>(result);
         Assert.False(result);
JFW.Sdk/Models/Users/UserCreateByEmailRequest.cs (2)

2-3: Namespace likely inconsistent with base type location

Base class appears under JFW.Sdk.Models.Users. Align namespaces for clarity and simpler imports.

-namespace JFW.Sdk.Models;
+namespace JFW.Sdk.Models.Users;

12-12: Consider adding basic validation annotations

If these DTOs are validated via DataAnnotations, mark EmailAddress accordingly.

+    [EmailAddress]
     public string EmailAddress { get; set; }

Remember to add:

using System.ComponentModel.DataAnnotations;
JFW.Sdk/Abstracts/ApiResponse.cs (3)

15-18: Prefer HttpStatusCode over int for status codes

Using the enum improves readability and avoids magic numbers.

-    public int StatusCode { get; set; }
+    public System.Net.HttpStatusCode StatusCode { get; set; }

25-29: Use IReadOnlyList for error collection

Arrays are mutable and harder to extend; IReadOnlyList better expresses intent and is friendlier for serializers.

-    public ApiErrorResponse[]? Errors { get; set; }
+    public IReadOnlyList<ApiErrorResponse>? Errors { get; init; }

39-49: Tighten nullability/immutability on error fields

These look required. Consider init-only and optionally add a minimal constructor.

-    public string Message { get; set; } = null!;
+    public string Message { get; init; } = null!;
-    public string Code { get; set; } = null!;
+    public string Code { get; init; } = null!;
-    public string CorrelationId { get; set; } = null!;
+    public string CorrelationId { get; init; } = null!;
JFW.Sdk/Clients/Implements/RolesClient.cs (2)

16-19: Fix copy/paste in XML doc: UsersClient → RolesClient

Minor doc correctness.

-    /// Initializes a new instance of the <see cref="UsersClient"/> class.
+    /// Initializes a new instance of the <see cref="RolesClient"/> class.

24-31: Validate id input to fail fast

Avoid round-trips on empty/whitespace ids.

-    public Task<Role?> GetByIdAsync(string id)
+    public Task<Role?> GetByIdAsync(string id)
     {
+        if (string.IsNullOrWhiteSpace(id))
+            throw new ArgumentException("id cannot be null or empty.", nameof(id));
         return Connection.GetAsync<Role>(
             UrlHelper.BuildUriRelative(BaseUri, id), // api/v1/roles/{id}
             DefaultHeaders
         );
     }
JFW.Sdk/Clients/Implements/IssueCategoriesClient.cs (1)

26-26: Unify generic type nullability with other clients

Use IssueCategory (not IssueCategory?) as the generic argument; the Task<T?> already communicates nullability.

JFW.Sdk/Models/Users/UserCreateByPhoneRequest.cs (2)

9-13: Fix XML doc: phone number, not email address

Minor doc accuracy.

-    /// The email address of the user to create.
+    /// The phone number of the user to create.

18-32: Optional: validate phone format at construction

Consider basic E.164 validation to catch mistakes early; alternatively, add a TODO to centralize validation.

JFW.Sdk/Models/IssueCategories/IssueCategory.cs (1)

44-48: Rename SuggestionURL → SuggestionUrl for .NET naming; map JSON with attribute

Keeps public API idiomatic while preserving wire format via [JsonProperty("suggestionURL")].

-    public string? SuggestionURL { get; set; }
+    // [JsonProperty("suggestionURL")] // add Newtonsoft.Json import if needed
+    public string? SuggestionUrl { get; set; }

Ensure serializer settings or attributes match the backend payload before renaming.

JFW.Sdk/Abstracts/HttpRequest.cs (1)

11-21: Consider making Uri and Payload nullable or init-only

If Payload is optional, mark it nullable. If Uri must always be provided, consider init-only to ensure construction-time assignment.

-        public string Uri { get; set; }
+        public string Uri { get; init; } = null!;
-        public object Payload { get; set; }
+        public object? Payload { get; set; }

Also applies to: 23-40

JFW.Sdk.Tests/RolesTests.cs (1)

38-41: Assert.NotNull before Assert.IsType for clearer failures

Minor ordering for diagnostics.

-        Assert.IsType<Role>(roleResult);
-        Assert.NotNull(roleResult);
+        Assert.NotNull(roleResult);
+        Assert.IsType<Role>(roleResult);
JFW.Sdk/IJfwApiClient.cs (1)

44-48: Reduce API surface: consider removing UpdateAuthKey in favor of AddOrUpdateDefaultHeader.
It’s a specialized duplicate of AddOrUpdateDefaultHeader(HeaderKeys.AuthKey, …). If you keep it, mark it as a convenience wrapper in docs.

JFW.Sdk/Clients/Implements/EventClient.cs (1)

25-40: Offer CancellationToken overloads.
SDK methods should allow cancellation/timeouts control in consumer apps.

If IManagementConnection supports it, add optional CancellationToken parameters and forward them.

JFW.Sdk/Clients/Interfaces/IUsersClient.cs (2)

60-66: Complete XML docs for GetAllAsync.
The summary/param/returns are empty; fill in endpoint and semantics.

-    /// <summary>
-    /// 
-    /// </summary>
-    /// <param name="request"></param>
-    /// <returns></returns>
+    /// <summary>
+    /// Gets a paged list of users filtered by the provided criteria.
+    /// API Endpoint: GET api/v1/users
+    /// </summary>
+    /// <param name="request">Filter and pagination criteria.</param>
+    /// <returns>Returns a paginated list of users, or null if no data.</returns>
     Task<PaginationList<User>?> GetAllAsync(GetUsersRequest request);

11-35: Consider CancellationToken support across the interface.
Add optional CancellationToken parameters to all methods for robustness in high-latency environments.

Also applies to: 37-59, 60-66

JFW.Sdk.Tests/EventsTests.cs (2)

66-87: Stabilize GetAll test with env config and minimal assertions.
Avoid real calls without config; reuse initialized auth key; ensure only runs when JFW_EVENT_CODE is set.

-        string eventCode = "Event_Code";
-
-        string authKey = "YOUR_AUTH_KEY";
-
-        _apiClients.UpdateAuthKey(authKey);
+        var eventCode = Environment.GetEnvironmentVariable("JFW_EVENT_CODE");
+        if (string.IsNullOrWhiteSpace(eventCode))
+            throw new Xunit.Sdk.SkipException("Set JFW_EVENT_CODE for this test.");
 ...
-                Code = eventCode
+                Code = eventCode

5-7: Namespace naming nit: place tests under a matching namespace.
Consider JFW.Sdk.Tests.Events to reflect file name.

-namespace JFW.Sdk.Tests.Features;
+namespace JFW.Sdk.Tests.Events;
JFW.Sdk/Abstracts/BaseClient.cs (2)

67-76: Guard string baseUri overload; trim slashes to keep stable URL composition.

     public BaseClient(
         IManagementConnection managementConnection,
         IDictionary<string, string> defaultHeaders,
         string baseUri
     ) : this(
         managementConnection,
         defaultHeaders,
-        new Uri(baseUri, UriKind.Relative)
+        new Uri(
+            string.IsNullOrWhiteSpace(baseUri)
+                ? throw new ArgumentException("baseUri cannot be null or whitespace.", nameof(baseUri))
+                : baseUri.TrimEnd('/'),
+            UriKind.Relative)
     )
     {
     }

4-7: Fill in XML docs for BaseClient and members.
Improves SDK IntelliSense.

JFW.Sdk/Models/Users/GetUsersRequest.cs (2)

47-63: Harden input normalization (trim/ignore blanks).

Avoid sending empty/whitespace query values and normalize Ids before join.

-        if (Ids != null && Ids.Any())
-            dictionary.Add("ids", string.Join(",", Ids));
+        if (Ids != null)
+        {
+            var cleaned = Ids.Where(s => !string.IsNullOrWhiteSpace(s))
+                             .Select(s => s.Trim())
+                             .ToArray();
+            if (cleaned.Length > 0)
+                dictionary.Add("ids", string.Join(",", cleaned));
+        }
 
-        if (!string.IsNullOrEmpty(RoleId))
-            dictionary.Add("roleId", RoleId);
+        if (!string.IsNullOrWhiteSpace(RoleId))
+            dictionary.Add("roleId", RoleId!.Trim());
 
-        if (!string.IsNullOrEmpty(Code))
-            dictionary.Add("code", Code);
+        if (!string.IsNullOrWhiteSpace(Code))
+            dictionary.Add("code", Code!.Trim());
 
-        if (!string.IsNullOrEmpty(FirstName))
-            dictionary.Add("firstName", FirstName);
+        if (!string.IsNullOrWhiteSpace(FirstName))
+            dictionary.Add("firstName", FirstName!.Trim());
 
-        if (!string.IsNullOrEmpty(LastName))
-            dictionary.Add("lastName", LastName);
+        if (!string.IsNullOrWhiteSpace(LastName))
+            dictionary.Add("lastName", LastName!.Trim());
 
-        if (!string.IsNullOrEmpty(NickName))
-            dictionary.Add("nickName", NickName);
+        if (!string.IsNullOrWhiteSpace(NickName))
+            dictionary.Add("nickName", NickName!.Trim());

39-43: Polish the XML summary.

-    /// Build to dictionary
+    /// Builds a dictionary of query parameters.
JFW.Sdk/Abstracts/JfwException.cs (2)

18-28: Ensure the parameterless ctor is safe and add inner-exception overload.

Keep a default message and code for the empty ctor; add an overload to preserve exception chaining.

-public JfwException() { }
+public JfwException() : base("Unknown error message") { }

 public JfwException(string code, string message) : base(message)
 {
     Code = code;
 }
+
+public JfwException(string code, string message, Exception? innerException) : base(message, innerException)
+{
+    Code = code;
+}

1-52: Consider capturing CorrelationId.

ApiErrorResponse exposes CorrelationId; surfacing it on JfwException improves diagnostics.

If helpful, I can add a CorrelationId property and wire it through constructors/factory.

JFW.Sdk/Abstracts/BaseJfwClient.cs (3)

37-41: Throw ArgumentException for invalid brandUrl.

NullReferenceException is the wrong choice for parameter validation.

- if (string.IsNullOrEmpty(brandUrl)) throw new NullReferenceException(nameof(brandUrl));
+ if (string.IsNullOrWhiteSpace(brandUrl))
+     throw new ArgumentException("brandUrl cannot be null or whitespace.", nameof(brandUrl));

31-33: Prefer configuring BaseAddress via DI, not inside the client.

Hardcoding BaseAddress reduces flexibility (multi-host, testing). Consider moving this to the HttpClient factory setup.


56-56: Rename the generic type parameter for clarity.

BaseResponse as a type parameter reads like a concrete type. Prefer TResponse.

Also applies to: 74-74, 92-92

JFW.Sdk/Models/Events/Event.cs (2)

14-18: Align doc with property name.

-/// The group name of the event.
+/// The group code name of the event.

39-43: Modeling of Tags.

If the API returns tags as an array, prefer a collection (e.g., List) over a comma-delimited string for type safety.

JFW.Sdk.Tests/UsersTest.cs (1)

24-49: Consider isolating external calls.

Prefer mocking IManagementConnection/HttpMessageHandler to validate URLs/headers without hitting the network; keep live tests behind a separate Integration test category.

Also applies to: 68-78

JFW.Sdk/Helpers/UrlHelper.cs (2)

18-24: Handle base URIs that already include query strings.

Current implementation always appends ?{queryString}. Use & when a query already exists.

Apply:

-    public static string BuildUriQuery(Uri baseUri, string? queryString)
+    public static string BuildUriQuery(Uri baseUri, string? queryString)
     {
         if (string.IsNullOrEmpty(queryString))
             return baseUri.ToString();
-        else
-            return $"{baseUri}?{queryString}";
+        var separator = baseUri.ToString().Contains('?') ? "&" : "?";
+        return $"{baseUri}{separator}{queryString}";
     }

Also applies to: 32-37, 60-65


72-89: Minor: avoid nullability mismatch and extra ToString.

queryStrings is non-null in callers. The null-conditional ? and trailing .ToString() on the Aggregate may hide errors. Keep it simple.

Apply:

-    public static string? AddQueryString(IDictionary<string, string> queryStrings)
+    public static string? AddQueryString(IDictionary<string, string> queryStrings)
     {
-        // Add the query strings
-        var queryString = queryStrings?.Aggregate(new StringBuilder(), (sb, kvp) =>
+        var queryString = queryStrings.Aggregate(new StringBuilder(), (sb, kvp) =>
             {
                 if (kvp.Value != null)
                 {
                     if (sb.Length > 0)
                         sb = sb.Append('&');

                     sb.Append($"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}");
                 }

                 return sb;
-            })
-            .ToString();
+            }).ToString();
         return queryString;
     }
JFW.Sdk/Apis/Implements/AuthApi.cs (1)

24-49: Propagate CancellationToken support.

Consider accepting a CancellationToken in each method and passing it to _client.PostAsync/GetAsync to avoid hangs on network issues.

Also applies to: 51-75, 77-101, 103-127, 129-153, 155-179

JFW.Sdk/ManagementConnection.cs (2)

53-58: Preserve serializer settings when custom converters are passed.

Currently, passing converters drops the default settings.

Apply:

-        return JsonConvert.DeserializeObject<ApiResponse<T>>(content,
-            converters == null ? jsonSerializerSettings : new JsonSerializerSettings() { Converters = converters });
+        var settings = converters == null
+            ? jsonSerializerSettings
+            : new JsonSerializerSettings
+              {
+                  NullValueHandling = NullValueHandling.Ignore,
+                  DateParseHandling = DateParseHandling.DateTime,
+                  ContractResolver = new CamelCasePropertyNamesContractResolver(),
+                  Converters = converters
+              };
+        return JsonConvert.DeserializeObject<ApiResponse<T>>(content, settings);

81-134: Add timeouts/retry policy at call-site or via HttpClient configuration.

Long-lived hangs hurt resilience. Consider setting HttpClient.Timeout and/or plugging a Polly policy externally.

JFW.Sdk/Exceptions/JfwException.cs (5)

69-81: Prefer HttpStatusCode over int for stronger typing.

Use the framework enum and keep ErrorCode numeric via cast.

Apply:

+using System.Net;
@@
-        public int StatusCode { get; }
+        public HttpStatusCode StatusCode { get; }
@@
-        public JfwApiException(string message, int statusCode) 
-            : base(message, $"API_ERROR_{statusCode}")
+        public JfwApiException(string message, HttpStatusCode statusCode) 
+            : base(message, $"API_ERROR_{(int)statusCode}")
         {
             StatusCode = statusCode;
         }
@@
-        public JfwApiException(string message, int statusCode, Exception innerException) 
-            : base(message, $"API_ERROR_{statusCode}", innerException)
+        public JfwApiException(string message, HttpStatusCode statusCode, Exception innerException) 
+            : base(message, $"API_ERROR_{(int)statusCode}", innerException)
         {
             StatusCode = statusCode;
         }

49-59: Seal leaf exceptions.

These are not meant for further inheritance; sealing clarifies intent and enables minor runtime optimizations.

Apply:

-    public class JfwAuthenticationException : JfwException
+    public sealed class JfwAuthenticationException : JfwException
@@
-    public class JfwApiException : JfwException
+    public sealed class JfwApiException : JfwException
@@
-    public class JfwConfigurationException : JfwException
+    public sealed class JfwConfigurationException : JfwException

Also applies to: 64-82, 87-97


18-43: Consider adding standard/serialization constructors if public SDK stability across boundaries matters.

If you target broad reuse, add parameterless and protected (SerializationInfo, StreamingContext) constructors and [Serializable] on types; also persist ErrorCode/StatusCode in GetObjectData. Otherwise, skip—.NET 6 apps rarely require binary serialization.

Do you need binary serialization or cross-AppDomain compatibility? If yes, I can generate the exact patches.


46-58: Centralize error code strings.

Avoid hardcoding "AUTH_ERROR"/"CONFIG_ERROR" in multiple places; define shared constants to prevent drift.

I can add a static JfwErrorCodes class and update call sites on request.

Also applies to: 84-96


8-9: Unify duplicate JfwException — rename Exceptions/JfwException.cs to JfwSdkException

Two definitions exist: JFW.Sdk/Abstracts/JfwException.cs and JFW.Sdk/Exceptions/JfwException.cs. Rename the Exceptions variant (and its file) to JfwSdkException (consider making it abstract) and update its derived classes (JfwAuthenticationException, JfwApiException, JfwConfigurationException) and all usages — or consolidate into a single canonical base if that was the intent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36d0268 and 9a9dbb7.

📒 Files selected for processing (58)
  • .gitignore (1 hunks)
  • CHANGELOG.md (1 hunks)
  • JFW.Sdk.Tests/EventsTests.cs (1 hunks)
  • JFW.Sdk.Tests/FeaturesTests.cs (1 hunks)
  • JFW.Sdk.Tests/JFW.Sdk.Tests.csproj (1 hunks)
  • JFW.Sdk.Tests/RolesTests.cs (1 hunks)
  • JFW.Sdk.Tests/UsersTest.cs (1 hunks)
  • JFW.Sdk.Tests/Usings.cs (1 hunks)
  • JFW.Sdk.sln (1 hunks)
  • JFW.Sdk/Abstracts/ApiResponse.cs (1 hunks)
  • JFW.Sdk/Abstracts/BaseClient.cs (1 hunks)
  • JFW.Sdk/Abstracts/BaseJfwClient.cs (1 hunks)
  • JFW.Sdk/Abstracts/BaseModel.cs (1 hunks)
  • JFW.Sdk/Abstracts/HttpRequest.cs (1 hunks)
  • JFW.Sdk/Abstracts/JfwException.cs (1 hunks)
  • JFW.Sdk/Abstracts/PaginationList.cs (1 hunks)
  • JFW.Sdk/Apis/Implements/AuthApi.cs (1 hunks)
  • JFW.Sdk/Apis/Interfaces/IAuthApi.cs (1 hunks)
  • JFW.Sdk/Clients/Implements/EventClient.cs (1 hunks)
  • JFW.Sdk/Clients/Implements/FeaturesClient.cs (1 hunks)
  • JFW.Sdk/Clients/Implements/IssueCategoriesClient.cs (1 hunks)
  • JFW.Sdk/Clients/Implements/RolesClient.cs (1 hunks)
  • JFW.Sdk/Clients/Implements/UsersClient.cs (1 hunks)
  • JFW.Sdk/Clients/Interfaces/IEventClient.cs (1 hunks)
  • JFW.Sdk/Clients/Interfaces/IFeaturesClient.cs (1 hunks)
  • JFW.Sdk/Clients/Interfaces/IIssueCategoriesClient.cs (1 hunks)
  • JFW.Sdk/Clients/Interfaces/IRolesClient.cs (1 hunks)
  • JFW.Sdk/Clients/Interfaces/IUsersClient.cs (1 hunks)
  • JFW.Sdk/Constants/HeaderKeys.cs (1 hunks)
  • JFW.Sdk/Constants/HostUrl.cs (1 hunks)
  • JFW.Sdk/Constants/JfwStatus.cs (1 hunks)
  • JFW.Sdk/Constants/UserTypes.cs (1 hunks)
  • JFW.Sdk/Exceptions/JfwException.cs (1 hunks)
  • JFW.Sdk/Extensions/ServiceCollectionExtensions.cs (1 hunks)
  • JFW.Sdk/GlobalUsing.cs (1 hunks)
  • JFW.Sdk/Helpers/UrlHelper.cs (1 hunks)
  • JFW.Sdk/IJfwApiClient.cs (1 hunks)
  • JFW.Sdk/IManagementConnection.cs (1 hunks)
  • JFW.Sdk/JFW.Sdk.csproj (1 hunks)
  • JFW.Sdk/JfwApiClient.cs (1 hunks)
  • JFW.Sdk/ManagementConnection.cs (1 hunks)
  • JFW.Sdk/Models/BasePagination.cs (1 hunks)
  • JFW.Sdk/Models/Events/Event.cs (1 hunks)
  • JFW.Sdk/Models/Events/GetEventsRequest.cs (1 hunks)
  • JFW.Sdk/Models/IssueCategories/IssueCategory.cs (1 hunks)
  • JFW.Sdk/Models/Options/JfwOptions.cs (1 hunks)
  • JFW.Sdk/Models/Requests/AuthenticationRequest.cs (1 hunks)
  • JFW.Sdk/Models/Requests/README.md (1 hunks)
  • JFW.Sdk/Models/Responses/AuthenticationResponse.cs (1 hunks)
  • JFW.Sdk/Models/Responses/BaseResponse.cs (1 hunks)
  • JFW.Sdk/Models/Roles/Role.cs (1 hunks)
  • JFW.Sdk/Models/Users/GetUsersRequest.cs (1 hunks)
  • JFW.Sdk/Models/Users/User.cs (1 hunks)
  • JFW.Sdk/Models/Users/UserBaseCreateRequest.cs (1 hunks)
  • JFW.Sdk/Models/Users/UserCreateByEmailRequest.cs (1 hunks)
  • JFW.Sdk/Models/Users/UserCreateByPhoneRequest.cs (1 hunks)
  • LICENSE (1 hunks)
  • README.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (33)
JFW.Sdk/Clients/Interfaces/IRolesClient.cs (1)
JFW.Sdk/Models/Roles/Role.cs (1)
  • Role (7-43)
JFW.Sdk/Models/Events/Event.cs (1)
JFW.Sdk/Abstracts/BaseModel.cs (1)
  • BaseModel (8-14)
JFW.Sdk/Clients/Interfaces/IUsersClient.cs (5)
JFW.Sdk/Models/Users/User.cs (1)
  • User (7-141)
JFW.Sdk/Models/Users/UserCreateByEmailRequest.cs (2)
  • UserCreateByEmailRequest (7-33)
  • UserCreateByEmailRequest (17-31)
JFW.Sdk/Models/Users/UserCreateByPhoneRequest.cs (2)
  • UserCreateByPhoneRequest (7-34)
  • UserCreateByPhoneRequest (18-32)
JFW.Sdk/Abstracts/PaginationList.cs (1)
  • PaginationList (8-19)
JFW.Sdk/Models/Users/GetUsersRequest.cs (1)
  • GetUsersRequest (7-67)
JFW.Sdk/Models/Roles/Role.cs (1)
JFW.Sdk/Abstracts/BaseModel.cs (1)
  • BaseModel (8-14)
JFW.Sdk/Apis/Interfaces/IAuthApi.cs (3)
JFW.Sdk/Models/Responses/AuthenticationResponse.cs (1)
  • AuthenticationResponse (14-40)
JFW.Sdk/Models/Requests/AuthenticationRequest.cs (1)
  • AuthenticationRequest (13-24)
JFW.Sdk/Models/Responses/BaseResponse.cs (1)
  • BaseResponse (13-15)
JFW.Sdk/Clients/Interfaces/IIssueCategoriesClient.cs (1)
JFW.Sdk/Models/IssueCategories/IssueCategory.cs (1)
  • IssueCategory (7-53)
JFW.Sdk/Abstracts/HttpRequest.cs (1)
JFW.Sdk/JfwApiClient.cs (1)
  • Dictionary (75-85)
JFW.Sdk/Models/Responses/AuthenticationResponse.cs (1)
JFW.Sdk/Models/Responses/BaseResponse.cs (1)
  • BaseResponse (13-15)
JFW.Sdk/Models/Users/GetUsersRequest.cs (1)
JFW.Sdk/Models/BasePagination.cs (1)
  • BasePagination (7-28)
JFW.Sdk/Apis/Implements/AuthApi.cs (6)
JFW.Sdk/Apis/Interfaces/IAuthApi.cs (6)
  • Task (11-11)
  • Task (16-16)
  • Task (21-21)
  • Task (26-26)
  • Task (31-31)
  • Task (36-36)
JFW.Sdk/Models/Responses/AuthenticationResponse.cs (1)
  • AuthenticationResponse (14-40)
JFW.Sdk/Models/Requests/AuthenticationRequest.cs (1)
  • AuthenticationRequest (13-24)
JFW.Sdk/Abstracts/HttpRequest.cs (1)
  • HttpRequest (8-73)
JFW.Sdk/Exceptions/JfwException.cs (3)
  • JfwAuthenticationException (49-59)
  • JfwAuthenticationException (51-53)
  • JfwAuthenticationException (55-58)
JFW.Sdk/Models/Responses/BaseResponse.cs (1)
  • BaseResponse (13-15)
JFW.Sdk/Clients/Interfaces/IEventClient.cs (3)
JFW.Sdk/Models/Events/Event.cs (1)
  • Event (7-58)
JFW.Sdk/Abstracts/PaginationList.cs (1)
  • PaginationList (8-19)
JFW.Sdk/Models/Events/GetEventsRequest.cs (1)
  • GetEventsRequest (7-99)
JFW.Sdk/Models/Users/UserCreateByPhoneRequest.cs (1)
JFW.Sdk/Models/Users/UserBaseCreateRequest.cs (1)
  • UserBaseCreateRequest (7-34)
JFW.Sdk/Clients/Implements/IssueCategoriesClient.cs (4)
JFW.Sdk/Abstracts/BaseClient.cs (4)
  • BaseClient (7-79)
  • BaseClient (35-44)
  • BaseClient (51-59)
  • BaseClient (67-77)
JFW.Sdk/Clients/Interfaces/IIssueCategoriesClient.cs (1)
  • Task (19-19)
JFW.Sdk/Models/IssueCategories/IssueCategory.cs (1)
  • IssueCategory (7-53)
JFW.Sdk/Helpers/UrlHelper.cs (2)
  • UrlHelper (9-90)
  • BuildUriRelative (45-51)
JFW.Sdk/Clients/Implements/FeaturesClient.cs (3)
JFW.Sdk/Abstracts/BaseClient.cs (4)
  • BaseClient (7-79)
  • BaseClient (35-44)
  • BaseClient (51-59)
  • BaseClient (67-77)
JFW.Sdk/Clients/Interfaces/IFeaturesClient.cs (1)
  • Task (18-18)
JFW.Sdk/Helpers/UrlHelper.cs (2)
  • UrlHelper (9-90)
  • BuildUri (60-65)
JFW.Sdk/ManagementConnection.cs (3)
JFW.Sdk/Abstracts/ApiResponse.cs (1)
  • ApiResponse (8-29)
JFW.Sdk/IManagementConnection.cs (2)
  • Task (27-35)
  • Task (46-46)
JFW.Sdk/Abstracts/JfwException.cs (5)
  • JfwException (8-52)
  • JfwException (18-18)
  • JfwException (25-28)
  • JfwException (34-37)
  • JfwException (43-51)
JFW.Sdk/Abstracts/ApiResponse.cs (1)
JFW.Sdk/ManagementConnection.cs (1)
  • ApiResponse (53-58)
JFW.Sdk.Tests/FeaturesTests.cs (4)
JFW.Sdk/JfwApiClient.cs (4)
  • JfwApiClient (12-104)
  • JfwApiClient (44-56)
  • JfwApiClient (65-67)
  • UpdateAuthKey (97-103)
JFW.Sdk/Clients/Implements/FeaturesClient.cs (1)
  • Task (26-38)
JFW.Sdk/Clients/Interfaces/IFeaturesClient.cs (1)
  • Task (18-18)
JFW.Sdk/IJfwApiClient.cs (1)
  • UpdateAuthKey (48-48)
JFW.Sdk/Models/Users/User.cs (1)
JFW.Sdk/Abstracts/BaseModel.cs (1)
  • BaseModel (8-14)
JFW.Sdk/Models/Events/GetEventsRequest.cs (1)
JFW.Sdk/Models/BasePagination.cs (1)
  • BasePagination (7-28)
JFW.Sdk/Models/Users/UserCreateByEmailRequest.cs (1)
JFW.Sdk/Models/Users/UserBaseCreateRequest.cs (1)
  • UserBaseCreateRequest (7-34)
JFW.Sdk/IJfwApiClient.cs (1)
JFW.Sdk/JfwApiClient.cs (2)
  • AddOrUpdateDefaultHeader (88-94)
  • UpdateAuthKey (97-103)
JFW.Sdk/Extensions/ServiceCollectionExtensions.cs (3)
JFW.Sdk/Models/Options/JfwOptions.cs (1)
  • JfwOptions (7-59)
JFW.Sdk/Abstracts/BaseJfwClient.cs (4)
  • BaseJFWClient (17-122)
  • BaseJFWClient (29-33)
  • BaseJFWClient (35-41)
  • BaseJFWClient (43-47)
JFW.Sdk/Apis/Implements/AuthApi.cs (2)
  • AuthApi (13-180)
  • AuthApi (18-22)
JFW.Sdk/Clients/Implements/RolesClient.cs (4)
JFW.Sdk/Abstracts/BaseClient.cs (4)
  • BaseClient (7-79)
  • BaseClient (35-44)
  • BaseClient (51-59)
  • BaseClient (67-77)
JFW.Sdk/Clients/Interfaces/IRolesClient.cs (1)
  • Task (17-17)
JFW.Sdk/Models/Roles/Role.cs (1)
  • Role (7-43)
JFW.Sdk/Helpers/UrlHelper.cs (2)
  • UrlHelper (9-90)
  • BuildUriRelative (45-51)
JFW.Sdk/Clients/Implements/EventClient.cs (5)
JFW.Sdk/Models/Events/GetEventsRequest.cs (2)
  • IDictionary (63-98)
  • GetEventsRequest (7-99)
JFW.Sdk/Clients/Interfaces/IEventClient.cs (2)
  • Task (20-20)
  • Task (30-30)
JFW.Sdk/Abstracts/PaginationList.cs (1)
  • PaginationList (8-19)
JFW.Sdk/Models/Events/Event.cs (1)
  • Event (7-58)
JFW.Sdk/Helpers/UrlHelper.cs (3)
  • UrlHelper (9-90)
  • AddQueryString (72-89)
  • BuildUriRelative (45-51)
JFW.Sdk/JfwApiClient.cs (6)
JFW.Sdk/Clients/Implements/IssueCategoriesClient.cs (2)
  • IssueCategoriesClient (10-28)
  • IssueCategoriesClient (18-21)
JFW.Sdk/Clients/Implements/FeaturesClient.cs (2)
  • FeaturesClient (11-39)
  • FeaturesClient (20-23)
JFW.Sdk/Clients/Implements/RolesClient.cs (2)
  • RolesClient (10-32)
  • RolesClient (19-22)
JFW.Sdk/Clients/Implements/UsersClient.cs (2)
  • UsersClient (11-65)
  • UsersClient (19-22)
JFW.Sdk/Constants/HeaderKeys.cs (1)
  • HeaderKeys (8-20)
JFW.Sdk/IJfwApiClient.cs (2)
  • AddOrUpdateDefaultHeader (42-42)
  • UpdateAuthKey (48-48)
JFW.Sdk.Tests/UsersTest.cs (6)
JFW.Sdk/ManagementConnection.cs (2)
  • ManagementConnection (12-176)
  • ManagementConnection (40-44)
JFW.Sdk/JfwApiClient.cs (5)
  • JfwApiClient (12-104)
  • JfwApiClient (44-56)
  • JfwApiClient (65-67)
  • AddOrUpdateDefaultHeader (88-94)
  • UpdateAuthKey (97-103)
JFW.Sdk/Clients/Implements/UsersClient.cs (5)
  • Task (26-29)
  • Task (32-35)
  • Task (38-44)
  • Task (47-53)
  • Task (56-63)
JFW.Sdk/Models/Users/UserCreateByEmailRequest.cs (2)
  • UserCreateByEmailRequest (7-33)
  • UserCreateByEmailRequest (17-31)
JFW.Sdk/Models/Users/User.cs (1)
  • User (7-141)
JFW.Sdk/Models/Users/UserCreateByPhoneRequest.cs (2)
  • UserCreateByPhoneRequest (7-34)
  • UserCreateByPhoneRequest (18-32)
JFW.Sdk/Abstracts/JfwException.cs (1)
JFW.Sdk/Abstracts/ApiResponse.cs (1)
  • ApiErrorResponse (34-50)
JFW.Sdk/Abstracts/BaseJfwClient.cs (3)
JFW.Sdk/Constants/HostUrl.cs (1)
  • HostUrl (4-7)
JFW.Sdk/Constants/HeaderKeys.cs (1)
  • HeaderKeys (8-20)
JFW.Sdk/Abstracts/HttpRequest.cs (2)
  • HttpRequest (8-73)
  • BuildUrlRequest (45-71)
JFW.Sdk.Tests/EventsTests.cs (7)
JFW.Sdk/JfwApiClient.cs (4)
  • JfwApiClient (12-104)
  • JfwApiClient (44-56)
  • JfwApiClient (65-67)
  • UpdateAuthKey (97-103)
JFW.Sdk/Clients/Interfaces/IEventClient.cs (2)
  • Task (20-20)
  • Task (30-30)
JFW.Sdk/Clients/Implements/EventClient.cs (2)
  • Task (26-33)
  • Task (36-40)
JFW.Sdk/IJfwApiClient.cs (1)
  • UpdateAuthKey (48-48)
JFW.Sdk/Models/Events/Event.cs (1)
  • Event (7-58)
JFW.Sdk/Models/Events/GetEventsRequest.cs (1)
  • GetEventsRequest (7-99)
JFW.Sdk/Abstracts/PaginationList.cs (1)
  • PaginationList (8-19)
JFW.Sdk.Tests/RolesTests.cs (5)
JFW.Sdk/JfwApiClient.cs (4)
  • JfwApiClient (12-104)
  • JfwApiClient (44-56)
  • JfwApiClient (65-67)
  • UpdateAuthKey (97-103)
JFW.Sdk/Clients/Implements/RolesClient.cs (1)
  • Task (25-31)
JFW.Sdk/Clients/Interfaces/IRolesClient.cs (1)
  • Task (17-17)
JFW.Sdk/IJfwApiClient.cs (1)
  • UpdateAuthKey (48-48)
JFW.Sdk/Models/Roles/Role.cs (1)
  • Role (7-43)
JFW.Sdk/Helpers/UrlHelper.cs (1)
JFW.Sdk/Models/Users/GetUsersRequest.cs (1)
  • IDictionary (43-66)
JFW.Sdk/Models/IssueCategories/IssueCategory.cs (1)
JFW.Sdk/Abstracts/BaseModel.cs (1)
  • BaseModel (8-14)
JFW.Sdk/Clients/Implements/UsersClient.cs (5)
JFW.Sdk/Abstracts/BaseClient.cs (4)
  • BaseClient (7-79)
  • BaseClient (35-44)
  • BaseClient (51-59)
  • BaseClient (67-77)
JFW.Sdk/Models/Users/GetUsersRequest.cs (2)
  • IDictionary (43-66)
  • GetUsersRequest (7-67)
JFW.Sdk/Clients/Interfaces/IUsersClient.cs (5)
  • Task (22-22)
  • Task (35-35)
  • Task (47-47)
  • Task (58-58)
  • Task (65-65)
JFW.Sdk/IManagementConnection.cs (3)
  • Task (27-35)
  • Task (46-46)
  • Task (59-59)
JFW.Sdk/Helpers/UrlHelper.cs (5)
  • UrlHelper (9-90)
  • BuildUriRelative (45-51)
  • AddQueryString (72-89)
  • BuildUriQuery (18-24)
  • BuildUriQuery (32-37)
🔇 Additional comments (16)
JFW.Sdk/Constants/UserTypes.cs (1)

18-18: Stabilize externally significant role strings — use fixed literals, not nameof

If these constants are part of persisted/API contracts, renaming fields will change emitted strings; replace nameof(...) with fixed literals.

File: JFW.Sdk/Constants/UserTypes.cs (also applies to lines 23, 28, 33)

-        public const string SuperAdmin = nameof(SuperAdmin);
+        public const string SuperAdmin = "SuperAdmin";
-        public const string Admin = nameof(Admin);
+        public const string Admin = "Admin";
-        public const string Partner = nameof(Partner);
+        public const string Partner = "Partner";
-        public const string User = nameof(User);
+        public const string User = "User";

Verify usages and any hard-coded role strings:

rg -n 'UserTypes.(SuperAdmin|Admin|Partner|User)' -C2
rg -n '"(SuperAdmin|Admin|Partner|User)"' -g '!JFW.Sdk/Constants/UserTypes.cs' -C2

If rg reports "No files were searched", run rg --debug or fallback to git grep -nE '(SuperAdmin|Admin|Partner|User)'.

JFW.Sdk/GlobalUsing.cs (1)

1-3: LGTM: sensible globals to cut boilerplate.
No functional concerns.

JFW.Sdk/JFW.Sdk.csproj (1)

16-22: Consolidate JSON serializers or justify keeping both

Newtonsoft.Json is used in JFW.Sdk/IManagementConnection.cs and JFW.Sdk/ManagementConnection.cs (JsonConvert.SerializeObject/DeserializeObject); System.Text.Json is used in JFW.Sdk/Abstracts/BaseJfwClient.cs (JsonSerializer.Deserialize). Carrying both increases package size and maintenance surface — remove or migrate to a single serializer unless you need features from both (specific converters, attribute behaviors, or compatibility).

.gitignore (1)

1-401: Solid baseline .gitignore.

Comprehensive and appropriate for a .NET SDK. No issues.

JFW.Sdk/IManagementConnection.cs (1)

28-29: Confirm HttpMethod availability via global usings.

This file doesn’t import System.Net.Http. If no global using exists, add it to avoid build breaks.

JFW.Sdk.Tests/Usings.cs (1)

1-1: LGTM.

Global using for xUnit is concise and keeps test files clean.

JFW.Sdk.Tests/JFW.Sdk.Tests.csproj (1)

11-22: Bump test tooling to latest stable versions (as of 2025-09-11)

Update packages in JFW.Sdk.Tests/JFW.Sdk.Tests.csproj to:

  • Microsoft.NET.Test.Sdk → 17.14.1
  • xunit → 2.9.3 (v3 is still pre-release)
  • xunit.runner.visualstudio → 3.1.4
  • coverlet.collector → 6.0.4

Run the full test suite and verify CI agents; consider enabling deterministic builds and Source Link.

JFW.Sdk/Abstracts/ApiResponse.cs (1)

8-29: Verify JSON contract alignment (PascalCase vs camelCase)

If the API returns camelCase fields, either ensure the JsonSerializerSettings use a CamelCaseContractResolver or add [JsonProperty] attributes to avoid deserialization misses.

Also applies to: 31-50

JFW.Sdk/Clients/Implements/RolesClient.cs (1)

10-15: LGTM on base URI composition

BaseUriClient + UrlHelper yields the expected api/v1/roles/{id} path.

JFW.Sdk/IJfwApiClient.cs (1)

12-36: Good aggregator surface; properties are cohesive and clear.
The client grouping (Events, IssueCategories, Features, Roles, Users) looks clean and discoverable.

JFW.Sdk/Abstracts/BaseClient.cs (1)

2-9: No action required — global using present.
JFW.Sdk/GlobalUsing.cs contains global using JFW.Sdk.Clients.Abstracts;, so IManagementConnection is available to BaseClient and no explicit using is needed.

JFW.Sdk/Models/Events/Event.cs (1)

12-18: Confirm dual identifiers (Id and Guid) are both required.

This model inherits Id (string?) and adds Guid (Guid). If both map to API fields, fine; otherwise consider consolidating to one to avoid confusion.

JFW.Sdk/JfwApiClient.cs (1)

49-56: Confirm clients don't clone or mutate the shared DefaultHeaders instance.
Passing the same headers object to all clients is acceptable only if each client stores the reference and does not copy or modify it. Verify constructors/field assignments in EventsClient, IssueCategoriesClient, FeaturesClient, RolesClient, and UsersClient (see JFW.Sdk/JfwApiClient.cs lines 49–56).

JFW.Sdk/Clients/Implements/UsersClient.cs (1)

56-63: Consider passing pagination to query.

If GetUsersRequest includes paging, ensure ToDictionary() adds pageNumber/pageSize (see related comment).

JFW.Sdk/Models/Events/GetEventsRequest.cs (1)

85-87: Enum serialization format.

Confirm the API expects status as string; if numeric is required, use ( (int)Status.Value ).ToString().

JFW.Sdk/Exceptions/JfwException.cs (1)

3-7: Overall: solid, readable hierarchy and XML docs.

Clear responsibilities and helpful properties for consumers.

Comment on lines +25 to +41
[Fact]
public async Task GetByIdAsync_Should_Result_DataCorrect()
{

string eventId = "EVENT_ID";

string authKey = "YOUR_AUTH_KEY";

_apiClients.UpdateAuthKey(authKey);

// Act
var result = await _apiClients.Events.GetByIdAsync(eventId);

// Assert
Assert.IsType<Event>(result);
Assert.NotNull(result);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Flaky: live endpoint + placeholder credentials will fail CI. Gate as integration via environment and skip if unset.
Also assert order can be simplified (NotNull before IsType).

Apply:

 namespace JFW.Sdk.Tests.Features;
 
 public class EventsTest
 {
-    private const string BrandUrl = "your.domain.com"; // Replace with your actual brand URL for testing
+    private readonly string? _brandUrl = Environment.GetEnvironmentVariable("JFW_BRAND_URL");
+    private readonly string? _baseUrl  = Environment.GetEnvironmentVariable("JFW_BASE_URL"); // e.g. https://{subdomain}.jframework.io
+    private readonly string? _authKey  = Environment.GetEnvironmentVariable("JFW_AUTH_KEY");
 
     private readonly IJfwApiClient _apiClients;
 
     public EventsTest()
     {
-        var baseUrl = "https://[subdomain].jframework.io";
+        if (string.IsNullOrWhiteSpace(_baseUrl) || string.IsNullOrWhiteSpace(_brandUrl) || string.IsNullOrWhiteSpace(_authKey))
+            throw new Xunit.Sdk.SkipException("Integration credentials not configured (JFW_BASE_URL, JFW_BRAND_URL, JFW_AUTH_KEY).");
 
-        var httpClient = new HttpClient();
-        httpClient.BaseAddress = new Uri(baseUrl);
+        var httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl!, UriKind.Absolute) };
 
         var connection = new ManagementConnection(httpClient);
 
-        _apiClients = new JfwApiClient(BrandUrl, connection);
+        _apiClients = new JfwApiClient(_brandUrl!, connection);
+        _apiClients.UpdateAuthKey(_authKey!);
     }
 
     [Fact]
     public async Task GetByIdAsync_Should_Result_DataCorrect()
     {
 
-        string eventId = "EVENT_ID";
-
-        string authKey = "YOUR_AUTH_KEY";
-
-        _apiClients.UpdateAuthKey(authKey);
+        // Provide a valid ID via env var to make this deterministic
+        var validId = Environment.GetEnvironmentVariable("JFW_VALID_EVENT_ID");
+        if (string.IsNullOrWhiteSpace(validId))
+            throw new Xunit.Sdk.SkipException("Set JFW_VALID_EVENT_ID for this test.");
 
         // Act
-        var result = await _apiClients.Events.GetByIdAsync(eventId);
+        var result = await _apiClients.Events.GetByIdAsync(validId!);
 
         // Assert
-        Assert.IsType<Event>(result);
         Assert.NotNull(result);
+        Assert.IsType<Event>(result);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[Fact]
public async Task GetByIdAsync_Should_Result_DataCorrect()
{
string eventId = "EVENT_ID";
string authKey = "YOUR_AUTH_KEY";
_apiClients.UpdateAuthKey(authKey);
// Act
var result = await _apiClients.Events.GetByIdAsync(eventId);
// Assert
Assert.IsType<Event>(result);
Assert.NotNull(result);
}
namespace JFW.Sdk.Tests.Features;
public class EventsTest
{
private readonly string? _brandUrl = Environment.GetEnvironmentVariable("JFW_BRAND_URL");
private readonly string? _baseUrl = Environment.GetEnvironmentVariable("JFW_BASE_URL"); // e.g. https://{subdomain}.jframework.io
private readonly string? _authKey = Environment.GetEnvironmentVariable("JFW_AUTH_KEY");
private readonly IJfwApiClient _apiClients;
public EventsTest()
{
if (string.IsNullOrWhiteSpace(_baseUrl) || string.IsNullOrWhiteSpace(_brandUrl) || string.IsNullOrWhiteSpace(_authKey))
throw new Xunit.Sdk.SkipException("Integration credentials not configured (JFW_BASE_URL, JFW_BRAND_URL, JFW_AUTH_KEY).");
var httpClient = new HttpClient { BaseAddress = new Uri(_baseUrl!, UriKind.Absolute) };
var connection = new ManagementConnection(httpClient);
_apiClients = new JfwApiClient(_brandUrl!, connection);
_apiClients.UpdateAuthKey(_authKey!);
}
[Fact]
public async Task GetByIdAsync_Should_Result_DataCorrect()
{
// Provide a valid ID via env var to make this deterministic
var validId = Environment.GetEnvironmentVariable("JFW_VALID_EVENT_ID");
if (string.IsNullOrWhiteSpace(validId))
throw new Xunit.Sdk.SkipException("Set JFW_VALID_EVENT_ID for this test.");
// Act
var result = await _apiClients.Events.GetByIdAsync(validId!);
// Assert
Assert.NotNull(result);
Assert.IsType<Event>(result);
}
}
🤖 Prompt for AI Agents
In JFW.Sdk.Tests/EventsTests.cs around lines 25 to 41 the test calls a live
endpoint using placeholder auth which makes the test flaky and will fail CI;
gate this as an integration test by checking for a specific environment variable
(e.g., INTEGRATION_TESTS or JFW_API_KEY) at the start of the test and call
Skip/Test.Skip/Assert.Skip (or use xUnit's Skip via conditional Fact) when the
env var is not set so the test only runs in integration environments, and
reorder the assertions to Assert.NotNull(result) before
Assert.IsType<Event>(result).

Comment on lines +43 to +64
[Fact]
public async Task GetByIdAsync_Should_Throw_NotFound()
{

string eventId = "EVENT_ID";

string authKey = "YOUR_AUTH_KEY";

_apiClients.UpdateAuthKey(authKey);

// Act
// throw BaseException if the id was not found
// Act & Assert
var exception = await Assert.ThrowsAsync<JfwException>(async () =>
{
await _apiClients.Events.GetByIdAsync(eventId);
});

// Assert
// Verify exception details if needed
Assert.Equal("EventNotFound", exception.Code);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

NotFound test uses the same ID as the success test; make it deterministically invalid.
Use a random GUID or env var for an invalid id.

-        string eventId = "EVENT_ID";
-
-        string authKey = "YOUR_AUTH_KEY";
-
-        _apiClients.UpdateAuthKey(authKey);
+        var invalidId = Guid.NewGuid().ToString("N");
 ...
-            await _apiClients.Events.GetByIdAsync(eventId);
+            await _apiClients.Events.GetByIdAsync(invalidId);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In JFW.Sdk.Tests/EventsTests.cs around lines 43 to 64, the NotFound test reuses
the same eventId as the success test which can make the failure
non-deterministic; replace the hardcoded "EVENT_ID" with a guaranteed-invalid
value such as Guid.NewGuid().ToString() or use an env var (e.g.,
Environment.GetEnvironmentVariable("INVALID_EVENT_ID") ??
Guid.NewGuid().ToString()) so the id is unique and deterministic for the
negative test, update the local variable assignment accordingly, and keep the
existing assertion checking exception.Code == "EventNotFound".

Comment on lines +12 to +20
var baseUrl = "https://[subdomain].jframework.io";

var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(baseUrl);

var connection = new ManagementConnection(httpClient);

_apiClients = new JfwApiClient(BrandUrl, connection);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Test uses an invalid BaseAddress and performs real HTTP calls

"https://[subdomain].jframework.io" is not a valid host; unit tests should not hit the network. Stub HttpMessageHandler to return a controlled response.

-        var baseUrl = "https://[subdomain].jframework.io";
-
-        var httpClient = new HttpClient();
-        httpClient.BaseAddress = new Uri(baseUrl);
+        var httpClient = new HttpClient(new FakeHandler(_ =>
+            new HttpResponseMessage(HttpStatusCode.OK)
+            {
+                Content = new StringContent("false", Encoding.UTF8, "application/json")
+            }))
+        {
+            BaseAddress = new Uri("https://unit.test")
+        };

Add this helper in the test file (or a shared test utilities file):

private sealed class FakeHandler : HttpMessageHandler
{
    private readonly Func<HttpRequestMessage, HttpResponseMessage> _responder;
    public FakeHandler(Func<HttpRequestMessage, HttpResponseMessage> responder) => _responder = responder;
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        => Task.FromResult(_responder(request));
}
🤖 Prompt for AI Agents
In JFW.Sdk.Tests/FeaturesTests.cs around lines 12-20, the test uses an invalid
BaseAddress ("https://[subdomain].jframework.io") and makes real HTTP calls;
replace real network calls by adding a FakeHandler HttpMessageHandler (as
suggested) to return controlled HttpResponseMessage instances, then construct
the HttpClient with that FakeHandler and set its BaseAddress to a valid URI or
the expected test URL, pass that HttpClient into ManagementConnection, and
update tests to assert against the fake responses rather than performing live
network calls.

Comment on lines +9 to +23
private const string BrandUrl = "your.domain.com"; // Replace with your actual brand URL for testing

private readonly IJfwApiClient _apiClients;

public RolesTests()
{
var baseUrl = "https://[subdomain].jframework.io";

var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(baseUrl);

var connection = new ManagementConnection(httpClient);

_apiClients = new JfwApiClient(BrandUrl, connection);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Tests rely on real HTTP endpoints and placeholders—will fail in CI

Gate these as integration tests or mock IManagementConnection to make them unit tests.

-    public RolesTests()
+    public RolesTests()
     {
-        var baseUrl = "https://[subdomain].jframework.io";
+        var baseUrl = "https://[subdomain].jframework.io"; // integration target
@@
-        _apiClients = new JfwApiClient(BrandUrl, connection);
+        _apiClients = new JfwApiClient(BrandUrl, connection);
     }

Follow-up options:

  • Quick: mark tests skipped until real values are provided:
-[Fact]
+[Fact(Skip = "Integration test: requires valid BaseUrl/AuthKey/RoleId")]
 public async Task GetByIdAsync_Should_Result_RoleData()

and similarly for the not-found test.

  • Better: mock IManagementConnection and assert call/URL composition.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private const string BrandUrl = "your.domain.com"; // Replace with your actual brand URL for testing
private readonly IJfwApiClient _apiClients;
public RolesTests()
{
var baseUrl = "https://[subdomain].jframework.io";
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(baseUrl);
var connection = new ManagementConnection(httpClient);
_apiClients = new JfwApiClient(BrandUrl, connection);
}
private const string BrandUrl = "your.domain.com"; // Replace with your actual brand URL for testing
private readonly IJfwApiClient _apiClients;
public RolesTests()
{
var baseUrl = "https://[subdomain].jframework.io"; // integration target
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(baseUrl);
var connection = new ManagementConnection(httpClient);
_apiClients = new JfwApiClient(BrandUrl, connection);
}
🤖 Prompt for AI Agents
JFW.Sdk.Tests/RolesTests.cs lines 9-23: tests construct a real
HttpClient/ManagementConnection against placeholder domains which makes them
integration tests and will fail in CI; replace this setup by mocking
IManagementConnection (or injecting a fake HttpClientFactory) so the
JfwApiClient is constructed with a mocked connection that returns canned
responses and allows asserting the request URL and behavior, or as a quick
alternative mark these tests with a skip/Ignore attribute until real brand/base
URLs are provided; ensure the mock verifies expected endpoint composition for
both success and not-found scenarios.

@@ -0,0 +1,98 @@

using JFW.Sdk.Models;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Missing usings likely to break compilation.

IJfwApiClient, JfwApiClient, ManagementConnection, and Fact/Assert come from other namespaces. If you don’t already have global usings, add these.

Apply:

 using JFW.Sdk.Models;
+using JFW.Sdk;
+using JFW.Sdk.Clients.Interfaces;
+using Xunit;

Run to confirm globals exist before applying:


🏁 Script executed:

#!/bin/bash
rg -nP '^\s*global\s+using\s+JFW\.Sdk\b' -g 'JFW.Sdk.Tests/**'
rg -nP '^\s*global\s+using\s+Xunit\b' -g 'JFW.Sdk.Tests/**'

Length of output: 45


Add missing usings: JFW.Sdk & JFW.Sdk.Clients.Interfaces (Xunit already global)

JFW.Sdk.Tests/Usings.cs contains global using Xunit; — add using JFW.Sdk; and using JFW.Sdk.Clients.Interfaces; at the top of JFW.Sdk.Tests/UsersTest.cs so IJfwApiClient, JfwApiClient and ManagementConnection resolve.

🤖 Prompt for AI Agents
In JFW.Sdk.Tests/UsersTest.cs around line 2, the file only has "using
JFW.Sdk.Models;" causing IJfwApiClient, JfwApiClient and ManagementConnection to
be unresolved; add "using JFW.Sdk;" and "using JFW.Sdk.Clients.Interfaces;" at
the top of this file (Xunit is already provided globally) so those types
resolve.

Comment on lines +16 to +40
/// <summary>
/// The unique identifier of the authenticated user
/// </summary>
public string Id { get; set; }

/// <summary>
/// The username of the authenticated user
/// </summary>
public string Username { get; set; }

/// <summary>
/// The email address of the authenticated user
/// </summary>
public string EmailAddress { get; set; }

/// <summary>
/// The authentication key for subsequent requests
/// </summary>
public string AuthKey { get; set; }

/// <summary>
/// The expiration time of the authentication token
/// </summary>
public DateTime ExpiresAt { get; set; }
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix nullability for non-null properties; consider DateTimeOffset

Non-nullable strings lack defaults and will trigger warnings at minimum. Use null-forgiving or ‘required’. Also prefer DateTimeOffset for expirations.

-        public string Id { get; set; }
+        public string Id { get; set; } = null!;
@@
-        public string Username { get; set; }
+        public string Username { get; set; } = null!;
@@
-        public string EmailAddress { get; set; }
+        public string EmailAddress { get; set; } = null!;
@@
-        public string AuthKey { get; set; }
+        public string AuthKey { get; set; } = null!;
@@
-        public DateTime ExpiresAt { get; set; }
+        public DateTimeOffset ExpiresAt { get; set; }

If the API returns a plain ISO-8601 without offset, keep DateTime.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// <summary>
/// The unique identifier of the authenticated user
/// </summary>
public string Id { get; set; }
/// <summary>
/// The username of the authenticated user
/// </summary>
public string Username { get; set; }
/// <summary>
/// The email address of the authenticated user
/// </summary>
public string EmailAddress { get; set; }
/// <summary>
/// The authentication key for subsequent requests
/// </summary>
public string AuthKey { get; set; }
/// <summary>
/// The expiration time of the authentication token
/// </summary>
public DateTime ExpiresAt { get; set; }
}
/// <summary>
/// The unique identifier of the authenticated user
/// </summary>
public string Id { get; set; } = null!;
/// <summary>
/// The username of the authenticated user
/// </summary>
public string Username { get; set; } = null!;
/// <summary>
/// The email address of the authenticated user
/// </summary>
public string EmailAddress { get; set; } = null!;
/// <summary>
/// The authentication key for subsequent requests
/// </summary>
public string AuthKey { get; set; } = null!;
/// <summary>
/// The expiration time of the authentication token
/// </summary>
public DateTimeOffset ExpiresAt { get; set; }
}

Comment on lines +39 to +66
/// <summary>
/// Build to dictionary
/// </summary>
/// <returns></returns>
public IDictionary<string, string> ToDictionary()
{
var dictionary = new Dictionary<string, string>();

if (Ids != null && Ids.Any())
dictionary.Add("ids", string.Join(",", Ids));

if (!string.IsNullOrEmpty(RoleId))
dictionary.Add("roleId", RoleId);

if (!string.IsNullOrEmpty(Code))
dictionary.Add("code", Code);

if (!string.IsNullOrEmpty(FirstName))
dictionary.Add("firstName", FirstName);

if (!string.IsNullOrEmpty(LastName))
dictionary.Add("lastName", LastName);

if (!string.IsNullOrEmpty(NickName))
dictionary.Add("nickName", NickName);

return dictionary;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Include pagination parameters in the query dictionary.

GetUsersRequest inherits BasePagination but ToDictionary() does not emit pageNumber/pageSize. This will ignore caller-specified pagination and rely on server defaults.

Apply near the return:

         if (!string.IsNullOrEmpty(NickName))
             dictionary.Add("nickName", NickName);
 
+        // Pagination (only when non-defaults)
+        if (PageNumber != BasePagination.PageNumberDefault)
+            dictionary.Add("pageNumber", PageNumber.ToString(System.Globalization.CultureInfo.InvariantCulture));
+        if (PageSize != BasePagination.PageSizeDefault)
+            dictionary.Add("pageSize", PageSize.ToString(System.Globalization.CultureInfo.InvariantCulture));
+
         return dictionary;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// <summary>
/// Build to dictionary
/// </summary>
/// <returns></returns>
public IDictionary<string, string> ToDictionary()
{
var dictionary = new Dictionary<string, string>();
if (Ids != null && Ids.Any())
dictionary.Add("ids", string.Join(",", Ids));
if (!string.IsNullOrEmpty(RoleId))
dictionary.Add("roleId", RoleId);
if (!string.IsNullOrEmpty(Code))
dictionary.Add("code", Code);
if (!string.IsNullOrEmpty(FirstName))
dictionary.Add("firstName", FirstName);
if (!string.IsNullOrEmpty(LastName))
dictionary.Add("lastName", LastName);
if (!string.IsNullOrEmpty(NickName))
dictionary.Add("nickName", NickName);
return dictionary;
}
/// <summary>
/// Build to dictionary
/// </summary>
/// <returns></returns>
public IDictionary<string, string> ToDictionary()
{
var dictionary = new Dictionary<string, string>();
if (Ids != null && Ids.Any())
dictionary.Add("ids", string.Join(",", Ids));
if (!string.IsNullOrEmpty(RoleId))
dictionary.Add("roleId", RoleId);
if (!string.IsNullOrEmpty(Code))
dictionary.Add("code", Code);
if (!string.IsNullOrEmpty(FirstName))
dictionary.Add("firstName", FirstName);
if (!string.IsNullOrEmpty(LastName))
dictionary.Add("lastName", LastName);
if (!string.IsNullOrEmpty(NickName))
dictionary.Add("nickName", NickName);
// Pagination (only when non-defaults)
if (PageNumber != BasePagination.PageNumberDefault)
dictionary.Add("pageNumber", PageNumber.ToString(System.Globalization.CultureInfo.InvariantCulture));
if (PageSize != BasePagination.PageSizeDefault)
dictionary.Add("pageSize", PageSize.ToString(System.Globalization.CultureInfo.InvariantCulture));
return dictionary;
}
🤖 Prompt for AI Agents
In JFW.Sdk/Models/Users/GetUsersRequest.cs around lines 39 to 66, ToDictionary()
currently omits pagination parameters inherited from BasePagination; add entries
for pageNumber and pageSize before the return so caller-specified pagination is
sent. Check the pagination properties (e.g., PageNumber and PageSize or their
nullable equivalents) and if they have values (or are > 0) add
dictionary.Add("pageNumber", PageNumber.ToString()) and
dictionary.Add("pageSize", PageSize.ToString()) using the same casing expected
by the API.

Comment on lines +14 to +17
The Okta software accompanied by this notice has build dependencies on certain
third party software licensed under separate terms ("Third Party Components")
located in THIRD_PARTY_NOTICES.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unrelated Okta reference block.
This appears to be a leftover and can confuse consumers.

Apply this diff:

-The Okta software accompanied by this notice has build dependencies on certain
-third party software licensed under separate terms ("Third Party Components")
-located in THIRD_PARTY_NOTICES.
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The Okta software accompanied by this notice has build dependencies on certain
third party software licensed under separate terms ("Third Party Components")
located in THIRD_PARTY_NOTICES.
🤖 Prompt for AI Agents
In LICENSE around lines 14 to 17, remove the leftover Okta reference block that
mentions Okta and Third Party Components; it is unrelated and confusing to
consumers. Edit the LICENSE file to delete those lines (the entire Okta/Third
Party Components paragraph) and ensure surrounding text remains grammatically
correct and that line breaks are adjusted so the license reads continuously
without the removed block.

Comment on lines +5 to +9
The SDK is compatible with:
- .NET Standard 2.0 and 2.1
- .NET Framework 6.0 or higher
- .NET Core 3.0 or higher
- .NET 6.0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct framework support to match the project (targets net6.0).
The list mentions “.NET Framework 6.0” (doesn’t exist) and broader TFMs not present in the csproj. Align docs with reality.

Apply this diff:

- The SDK is compatible with:
-- .NET Standard 2.0 and 2.1
-- .NET Framework 6.0 or higher
-- .NET Core 3.0 or higher
-- .NET 6.0
+ The SDK currently targets .NET 6.0 (net6.0).
+ Multi-targeting (e.g., net7.0/net8.0/netstandard2.1) may be added in future releases.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The SDK is compatible with:
- .NET Standard 2.0 and 2.1
- .NET Framework 6.0 or higher
- .NET Core 3.0 or higher
- .NET 6.0
The SDK currently targets .NET 6.0 (net6.0).
Multi-targeting (e.g., net7.0/net8.0/netstandard2.1) may be added in future releases.
🤖 Prompt for AI Agents
In README.md around lines 5 to 9 the supported frameworks list is incorrect (it
mentions .NET Standard, .NET Framework 6.0 which does not exist, and other TFMs
not present in the project); replace that list so it matches the project TFM(s)
in the .csproj (e.g., state only ".NET 6.0 (net6.0)" or the exact TFMs found in
the csproj), removing ".NET Framework 6.0", ".NET Standard 2.0 and 2.1", and
".NET Core 3.0 or higher".

Comment on lines +12 to +13
1. Right-click on your project in the Solution Explorer and choose Manage Nuget Packages...
2. Search for Okta. Install the `Jfw.Sdk` package.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix package manager instructions (wrong vendor) and casing.
Replace “Okta” with “Jfw.Sdk” and use “NuGet”.

Apply this diff:

-### Install using Nuget Package Manager
+### Install using NuGet Package Manager
 1. Right-click on your project in the Solution Explorer and choose Manage Nuget Packages...
-2. Search for Okta. Install the `Jfw.Sdk` package.
+2. Search for "Jfw.Sdk" and install the `Jfw.Sdk` package.
+
+Or via CLI:
+
+```bash
+dotnet add package Jfw.Sdk
+```
🤖 Prompt for AI Agents
In README.md around lines 12-13, replace the vendor name "Okta" with "Jfw.Sdk"
and correct the casing of "Nuget" to "NuGet"; then add a bash code block
immediately after the two numbered steps containing the command dotnet add
package Jfw.Sdk so users can install the package via CLI.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

/// <summary>
/// The error code associated with the exception
/// </summary>
public string ErrorCode { get; }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix potential NRT warning: ErrorCode may be uninitialized.

Under enable, this non-nullable auto-property isn’t assigned in all constructors.

Apply:

-        public string ErrorCode { get; }
+        public string ErrorCode { get; } = string.Empty;

Optionally, set a default in the message-only constructors (e.g., "UNKNOWN_ERROR") if having a non-empty code helps downstream logic.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public string ErrorCode { get; }
public string ErrorCode { get; } = string.Empty;
🤖 Prompt for AI Agents
In JFW.Sdk/Exceptions/JfwException.cs around line 13, the non-nullable
auto-property ErrorCode may be uninitialized under Nullable enable; update the
class so ErrorCode is always initialized: either make it nullable (string?) or
assign a default value on declaration, and ensure every constructor sets
ErrorCode (for message-only constructors set a sensible default like
"UNKNOWN_ERROR" if callers expect a non-empty code). Ensure all overloads assign
the property to eliminate the NRT warning.

Comment on lines +103 to +114
// Add headers
if (headers != null)
{
foreach (var header in headers)
{
if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value))
{
request.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Special-case restricted headers (e.g., User-Agent).

Falling back to request.Content.Headers drops general headers. Handle User-Agent explicitly; otherwise throw on unknown content headers.

Apply:

         if (headers != null)
         {
             foreach (var header in headers)
             {
-                if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value))
-                {
-                    request.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value);
-                }
+                if (header.Key.Equals("User-Agent", StringComparison.OrdinalIgnoreCase))
+                {
+                    request.Headers.TryAddWithoutValidation("User-Agent", header.Value);
+                    continue;
+                }
+                if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value))
+                    request.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value);
             }
         }
🤖 Prompt for AI Agents
In JFW.Sdk/ManagementConnection.cs around lines 103-114, the current header
fallback adds restricted headers (like User-Agent) to request.Content.Headers
which drops general headers and can silently fail; update the logic to
special-case restricted headers by detecting "User-Agent" (case-insensitive) and
set it on request.Headers using the appropriate UserAgent property or TryAdd on
request.Headers, handle other known restricted headers similarly, and if a
header cannot be applied to either request.Headers or request.Content.Headers
throw a clear exception instead of silently ignoring it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants