Skip to content

feat: add ccrwrapper endpoints - #196

Merged
SondreJDigdir merged 14 commits into
masterfrom
feat/ccrwrapper
Nov 3, 2025
Merged

SondreJDigdir merged 14 commits into
masterfrom
feat/ccrwrapper

Conversation

@SondreJDigdir

@SondreJDigdir SondreJDigdir commented Jun 19, 2025 •

Copy link
Copy Markdown
Contributor

Description

Need to be able to call ccr without identifier and with query parameters. This splits out the ccr lookup from the opendata endpoint

Documentation

  • Doc updated

Summary by CodeRabbit

  • New Features

    • CCR client for org lookups, subunit queries, public-agency checks and hierarchy retrieval.
    • HTTP endpoints exposing CCR lookups (organization, subunits, main unit, public check, hierarchy).
    • New public models for paginated registry responses, unit hierarchies and a CCR URL setting.
    • Plugin test endpoint and evidence code for plugin tests.
    • CCR-specific exception for structured error reporting.
  • Deprecations

    • Legacy entity-registry services and clients marked obsolete; migrate to the CCR client service.
  • Chores

    • Multiple dependency upgrades across projects; removed an old proxy endpoint.

@SondreJDigdir
SondreJDigdir marked this pull request as draft July 7, 2025 10:18
@sonarqubecloud

Copy link
Copy Markdown

@SondreJDigdir
SondreJDigdir marked this pull request as ready for review July 15, 2025 13:34
@coderabbitai

coderabbitai Bot commented Oct 27, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a CCR HTTP client and exception, BRREG pagination and hierarchy models, new core registry interfaces and implementations with caching and hierarchy logic, new Azure Functions for CCR lookups, deprecates legacy registry APIs, refactors caching to distributed cache, removes a PPE proxy function, and bumps many NuGet packages.

Changes

Cohort / File(s) Summary
Dependency bumps
Dan.Common.UnitTest/Dan.Common.UnitTest.csproj, Dan.Common/Dan.Common.csproj, Dan.Core.UnitTest/Dan.Core.UnitTest.csproj, Dan.Core/Dan.Core.csproj, Dan.PluginTest/Dan.PluginTest.csproj
Updated many NuGet package versions across projects (FluentAssertions, MSTest, Microsoft.Extensions., Azure., Functions Worker, Redis, Newtonsoft.Json, etc.).
CCR client & exception
Dan.Common/Services/CcrClientService.cs, Dan.Common/Services/ICcrClientService (new), Dan.Common/Exceptions/CcrException.cs
Added ICcrClientService and CcrClientService implementing HTTP CCR lookups (env-specific base URLs, synthetic-number gating, centralized response handling) and CcrException for mapped error codes.
DI registration
Dan.Common/Extensions/HostBuilderExtensions.cs
Registered ICcrClientService → CcrClientService as a transient service.
BRREG models & entity types
Dan.Common/Models/BrregPage.cs, Dan.Common/Models/EntityRegistryUnit.cs, Dan.Common/Models/EntityRegistryUnitHierarchy.cs
Added generic BrregPage<T> and supporting link/page types; added Underenheter to EntityRegistryUnit; added recursive EntityRegistryUnitHierarchy.
Obsolete markers & migration
Dan.Common/Interfaces/IEntityRegistryApiClientService.cs, Dan.Common/Interfaces/IEntityRegistryService.cs, Dan.Common/Services/DefaultEntityRegistryApiClientService.cs
Marked legacy common registry interfaces/classes obsolete and pointed to the new CCR client/service.
Core interfaces & implementations
Dan.Core/Services/Interfaces/IEntityRegistryApiClientService.cs, Dan.Core/Services/Interfaces/IEntityRegistryService.cs, Dan.Core/Services/CachingEntityRegistryApiClientService.cs, Dan.Core/Services/EntityRegistryService.cs
Added core interfaces for upstream client and registry service; refactored CachingEntityRegistryApiClientService to a partial primary-constructor form using ILoggerFactory/IHttpClientFactory/IDistributedCache, added list retrieval and distributed-cache usage; added EntityRegistryService with caching, hierarchy building, and IsPublic detection.
Program, settings & policies
Dan.Core/Config/Settings.cs, Dan.Core/Program.cs
Added Settings.CcrUrl; updated DI registrations to explicit namespaced interface/concrete types; added a cache policy entry for entity list.
Azure Functions (CCR wrapper & plugin)
Dan.Core/FuncCcrWrapper.cs, Dan.PluginTest/Plugin.cs, Dan.PluginTest/Config/PluginConstants.cs, Dan.PluginTest/Metadata.cs
Added FuncCcrWrapper with five HTTP endpoints delegating to registry service; injected ICcrClientService into plugin; added PluginGenericTest function plus a new evidence code and plugin constant.
Removed PPE proxy
Dan.Core/FuncPpeProxy.cs
Removed the PPE BRREG proxy Azure Function file.
Constructor/type adjustments across core services/functions
Dan.Core/FuncConsentReceipt.cs, Dan.Core/FuncOpenData.cs, Dan.Core/Services/AltinnCorrespondenceService.cs, Dan.Core/Services/AuthorizationRequestValidatorService.cs, Dan.Core/Services/ConsentService.cs, Dan.Core/Services/RequirementValidationService.cs
Replaced usages of IEntityRegistryService with Dan.Core.Services.Interfaces.IEntityRegistryService and removed inline init side-effects (UseCoreProxy, AllowTestCcrLookup) from constructors.
Caching refactor & constants
Dan.Core/Services/CachingEntityRegistryApiClientService.cs
Added EntityRegistryListCachePolicy constant, distributed-cache-backed list retrieval, pagination aggregation, and primary-constructor DI shape.
Middleware tweak
Dan.Core/Middleware/ExceptionHandlerMiddleware.cs
Changed WriteAsJsonAsync call to omit explicit statusCode parameter.
Unit test updates
Dan.Core.UnitTest/Services/EntityRegistryServiceTest.cs, Dan.Core.UnitTest/AuthorizationRequestValidatorServiceTest.cs, Dan.Core.UnitTest/ConsentServiceTest.cs, Dan.Core.UnitTest/RequirementValidationServiceTest.cs, Dan.Core.UnitTest/Dan.Core.UnitTest.csproj
Added tests for EntityRegistryService; removed unused using directives; adjusted tests to new constructor signatures and model shapes; bumped test project packages.
Global using
Dan.Common/GlobalUsings.cs
Added global using System.Net.Http;.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • CcrClientService: HTTP status-to-error mapping, synthetic-number gating, deserialization error handling, and environment base URLs.
  • Distributed cache refactor in CachingEntityRegistryApiClientService: cache keys, TTLs, serialization, pagination aggregation, and concurrency.
  • EntityRegistryService: recursive GetSubunitHierarchy, caching semantics, and IsPublicAgency heuristics.
  • BRREG JSON mappings in BrregPage<T> vs actual BRREG responses (_embedded, _links, page).
  • DI changes in Program.cs / HostBuilderExtensions: ensure registrations align and legacy interfaces aren’t left unresolved.
  • New Azure Functions and PluginGenericTest: routes, bindings, input parsing, NotFound/null handling, and the unused IsPublic call in the plugin.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: add ccrwrapper endpoints" is directly and clearly related to the main change in this changeset. The primary feature being added is the new FuncCcrWrapper class in Dan.Core/FuncCcrWrapper.cs that exposes five HTTP-triggered Azure Functions for CCR operations (organization number lookup, subunits, main unit, public agency check, and hierarchy). The title accurately summarizes this core functionality and aligns with the PR objectives, which explicitly state the goal is to add CCR wrapper endpoints to enable calling CCR without an identifier. While the changeset includes substantial supporting infrastructure (new ICcrClientService, refactored EntityRegistryService, new models, and dependency updates), these are secondary to the primary feature of adding the wrapper endpoints. The title is concise, specific, and communicates the main change clearly.
Docstring Coverage ✅ Passed Docstring coverage is 48.05% which is sufficient. The required threshold is 0.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ccrwrapper

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Dan.PluginTest/Plugin.cs (1)

16-22: Remove unused variable and externalize hard-coded values in PluginGenericTest

Line 141: The unit variable is assigned but never used. Additionally, the call contains hard-coded account ID ("923609016") and environment ("local") that should be externalized to configuration or derived from runtime context.

var unit = await ccrClientService.IsPublic("923609016", "local");

Either complete the feature (use the result) or remove the call. If needed, move the CCR validation logic into a proper service method with injected configuration for account ID and environment resolution.

Dan.Common/Services/EntityRegistryService.cs (1)

113-123: Guard against null OverordnetEnhet before lookup.

The do/while dereferences OverordnetEnhet via null-forgiving and can issue a bogus upstream call (e.g., “.../underenheter/”) if OverordnetEnhet is null. Switch to a guarded while and bail if lookup fails.

Apply:

-        var parentUnit = unit;
-        do
-        {
-            // Only subunits are at the leaf node, any nested parents are MainUnits
-            // Example: https://data.brreg.no/enhetsregisteret/api/underenheter/879587662
-            parentUnit = await InternalGet(parentUnit.OverordnetEnhet!, UnitType.MainUnit);
-        }
-        while (parentUnit?.OverordnetEnhet != null);
-        unit = parentUnit;
+        var parentUnit = unit;
+        while (!string.IsNullOrEmpty(parentUnit.OverordnetEnhet))
+        {
+            var nextOrg = parentUnit.OverordnetEnhet!;
+            var next = await InternalGet(nextOrg, UnitType.MainUnit);
+            if (next is null) break; // upstream returned nothing; stop nesting
+            parentUnit = next;
+        }
+        unit = parentUnit;
🧹 Nitpick comments (21)
Dan.Core/Services/Interfaces/IEntityRegistryApiClientService.cs (1)

9-20: Document the temporary namespace duplication.

Having IEntityRegistryApiClientService in both Dan.Common.Interfaces and Dan.Core.Services.Interfaces may cause confusion. Consider adding an XML comment noting this is the preferred interface and the Common version is deprecated.

Example addition:

 /// <summary>
 /// API Service for fetching entity registries
 /// </summary>
+/// <remarks>
+/// This interface supersedes Dan.Common.Interfaces.IEntityRegistryApiClientService which is deprecated.
+/// </remarks>
 
 public interface IEntityRegistryApiClientService
Dan.Core/Services/ConsentService.cs (2)

204-211: Sanitize logging to avoid leaking URLs/authorization artifacts

Avoid logging full URLs/request messages that may include sensitive values. Log only operation and status.

- _logger.LogInformation("Getting jwt: " + url);
+ _logger.LogInformation("Getting JWT from Altinn consent status endpoint");
...
- _logger.LogInformation($"JWT-get {result.RequestMessage} result: {result.StatusCode} : {result.ReasonPhrase}");
+ _logger.LogInformation("JWT-get result: {StatusCode} : {Reason}", result.StatusCode, result.ReasonPhrase);

459-461: Use culture-safe, allocation-free comparison

Use OrdinalIgnoreCase instead of ToLower().Contains to avoid culture issues and allocs.

- if (!Settings.AltinnServiceOwnerApiUri.ToLower().Contains("tt02"))
+ if (!Settings.AltinnServiceOwnerApiUri.Contains("tt02", StringComparison.OrdinalIgnoreCase))
Dan.Core/Services/AuthorizationRequestValidatorService.cs (1)

20-21: Entity registry interface swap: OK; confirm DI and env flag handling

The type switch aligns with the CCR refactor. Ensure DI is updated and that AllowTestCcrLookup isn’t toggled on a singleton instance.

Reuse the DI verification script from the ConsentService comment to confirm registrations and service lifetime.

Consider moving the environment toggle into DI configuration to avoid runtime mutation of shared state.

Also applies to: 38-40, 49-50

Dan.Core/Services/RequirementValidationService.cs (2)

20-21: Entity registry interface swap and env flag: looks fine

Type change is consistent with the migration. Same note on DI and mutable AllowTestCcrLookup as in other services.

Confirm DI registration and service lifetime using the earlier script.

Prefer configuring env behavior via options at registration time.

Also applies to: 35-38, 47-48


65-83: Avoid Task.Run around already-async work

Wrapping an async method in Task.Run adds unnecessary scheduling and overhead. Collect the Task directly.

-        var taskList = new List<Task>();
+        var taskList = new List<Task>();
         foreach (var er in _authRequest.EvidenceRequests)
         {
             ...
             foreach (var req in evidenceCodeRequirements[er.EvidenceCodeName])
             {
                 if (req.AppliesToServiceContext.Count > 0 && !req.AppliesToServiceContext.Contains(_requestContextService.ServiceContext.Name)) continue;
-
-                taskList.Add(Task.Run(() => ValidateSingleRequirement(req, er.EvidenceCodeName)));
+                taskList.Add(ValidateSingleRequirement(req, er.EvidenceCodeName));
             }
         }
 
         await Task.WhenAll(taskList.ToArray());
Dan.Core/FuncOpenData.cs (1)

82-107: Decouple from EntityRegistryService constants and align with new CCR wrapper

FuncOpenData still special-cases CCR datasets and references concrete EntityRegistryService.CcrProxy* constants. With CCR lookups moving to dedicated endpoints, consider:

  • Removing this CCR branch or delegating to the new CCR wrapper.
  • Moving the dataset-name constants to a neutral place (config/constants) to avoid coupling to a concrete class.

Would you like a follow-up diff to retire this path and route callers to FuncCcrWrapper?

Also applies to: 105-108

Dan.Common/Models/BrregPage.cs (1)

1-1: Json.NET attributes resolve via global using; optional DTO improvements available

Newtonsoft.Json is already globally available via Dan.Common/GlobalUsings.cs, so no file-level using is required.

Consider sealing these DTO classes or using record types for immutability and clarity. Optionally rename Subunits to BrregSubunits for clearer call sites.

Dan.Core.UnitTest/Services/EntityRegistryServiceTest.cs (1)

62-67: Prefer async/await over .Result in tests

Avoid blocking on Tasks in MSTest. Convert these tests to async Task and await the calls for clarity and to prevent deadlocks.

Example for one test (apply similarly to the others):

- [TestMethod]
- public void TestGetMapping()
+ [TestMethod]
+ public async Task TestGetMapping()
 {
-    var s = new EntityRegistryService(entityRegistryApiClientServiceMock.Object, logger.Object);
-    var r = s.Get("91").Result;
+    var s = new EntityRegistryService(entityRegistryApiClientServiceMock.Object, logger.Object);
+    var r = await s.Get("91");
     Assert.AreEqual("91", r?.OrganizationNumber);
 }

Also applies to: 70-75, 78-83, 86-91, 94-99, 102-107, 110-115, 118-123, 126-131, 134-139

Dan.Core/Services/Interfaces/IEntityRegistryService.cs (2)

16-23: Fix XML summaries: methods return units, not “organization number”

Adjust summaries to reflect that these methods retrieve entity registry “units” (simple or full), not just the number.

Also applies to: 26-33


23-33: Consider adding CancellationToken to public API

These methods perform network I/O downstream. Expose CancellationToken parameters (optional with default) to propagate cancellations from HTTP requests and background jobs. This is easier to do now before the interface is widely consumed.

Also applies to: 35-41, 47-69

Dan.Core/Services/CachingEntityRegistryApiClientService.cs (1)

121-126: Log exception details on deserialization failures

Capture the exception to aid diagnostics.

-        catch (Exception)
-        {
-            logger.LogError("Failed deserializing from BR on {registryApiUri}", registryApiUri);
-            throw;
-        }
+        catch (Exception ex)
+        {
+            logger.LogError(ex, "Failed deserializing from BR on {RegistryApiUri}", registryApiUri);
+            throw;
+        }
Dan.Core/Services/EntityRegistryService.cs (2)

270-279: Build list-lookup URI without a stray slash

Using ".../underenheter/{0}" with "?overordnetEnhet=..." yields .../underenheter/?overordnetEnhet=.... Prefer a dedicated list endpoint pattern to avoid reliance on server tolerance.

-    private static Uri GetLookupUrlForSubunitsOfAUnit(string organizationNumber)
-    {
-        var urlPattern = IsSyntheticOrganizationNumber(organizationNumber) ? 
-            PpeSubUnitLookupEndpoint : 
-            SubUnitLookupEndpoint;
-        
-        var query = $"?overordnetEnhet={organizationNumber}";
-
-        return new Uri(string.Format(urlPattern, query));
-    }
+    private static Uri GetLookupUrlForSubunitsOfAUnit(string organizationNumber)
+    {
+        var baseUrl = IsSyntheticOrganizationNumber(organizationNumber)
+            ? "https://data.ppe.brreg.no/enhetsregisteret/api/underenheter"
+            : "https://data.brreg.no/enhetsregisteret/api/underenheter";
+        return new Uri($"{baseUrl}?overordnetEnhet={organizationNumber}");
+    }

195-198: Defensive check in IsSyntheticOrganizationNumber

Avoid StartsWith on null/empty and use char-check for speed.

-    private static bool IsSyntheticOrganizationNumber(string organizationNumber)
-    {
-        return organizationNumber.StartsWith('2') || organizationNumber.StartsWith('3');
-    }
+    private static bool IsSyntheticOrganizationNumber(string organizationNumber)
+    {
+        if (string.IsNullOrEmpty(organizationNumber)) return false;
+        var c = organizationNumber[0];
+        return c == '2' || c == '3';
+    }
Dan.Core/FuncCcrWrapper.cs (1)

12-29: Validate orgNumber and return 400 on invalid input

Early reject clearly invalid org numbers (e.g., non-digits, wrong length) to save downstream calls and clarify client errors.

Example (apply to each handler):

         public async Task<HttpResponseData> RunCcrOrgnumberLookup(
             [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "ccr/{orgNumber}")] HttpRequestData req,
             ILogger log,
             string orgNumber)
         {
+            if (string.IsNullOrWhiteSpace(orgNumber) || orgNumber.Length != 9 || !orgNumber.All(char.IsDigit))
+            {
+                var bad = req.CreateResponse(HttpStatusCode.BadRequest);
+                await bad.WriteStringAsync("orgNumber must be 9 digits.");
+                return bad;
+            }
             var unit = await entityRegistryService.GetFull(orgNumber, attemptSubUnitLookupIfNotFound: true);

Also applies to: 31-42, 44-61, 63-74, 76-93

Dan.Common/Services/EntityRegistryService.cs (1)

201-204: Minor: align synthetic check with char-based variant used elsewhere.

Slightly faster and consistent with CcrClientService.

-    private bool IsSyntheticOrganizationNumber(string organizationNumber)
-    {
-        return organizationNumber.StartsWith("2") || organizationNumber.StartsWith("3");
-    }
+    private static bool IsSyntheticOrganizationNumber(string organizationNumber) =>
+        !string.IsNullOrEmpty(organizationNumber) && (organizationNumber[0] is '2' or '3');
Dan.Common/Services/CcrClientService.cs (5)

145-156: Add cancellation support in HTTP calls.

Improve resiliency by allowing call sites to cancel requests.

-    private async Task<T?> GetCcrResponse<T>(string url)
+    private async Task<T?> GetCcrResponse<T>(string url, CancellationToken ct = default)
     {
         HttpResponseMessage response;
         try
         {
-            response = await httpClient.GetAsync(url);
+            response = await httpClient.GetAsync(url, ct);
         }

(Calls don’t need changes due to default parameter; optionally thread a token through later.)

Also applies to: 150-156


161-166: Handle 200 OK with empty body.

Avoid deserializing empty content; treat as not found/none.

-                    var responseString = await response.Content.ReadAsStringAsync();
-                    var unit = JsonConvert.DeserializeObject<T>(responseString);
+                    var responseString = await response.Content.ReadAsStringAsync();
+                    if (string.IsNullOrWhiteSpace(responseString))
+                        return default;
+                    var unit = JsonConvert.DeserializeObject<T>(responseString);
                     return unit;

154-156: Log URL for failed requests.

Adds context to logs without leaking payload.

-            logger.LogError(e, "Failed to get CCR response: {message}", e.Message);
+            logger.LogError(e, "Failed to get CCR response. GET {url}. Error: {message}", url, e.Message);

58-63: Nit: fix “Crr” vs “Ccr” constant naming.

Cosmetic consistency only.

-    private const string CrrBaseDev = "https://dev-api.data.altinn.no/v1/public/ccr";
-    private const string CrrBaseTest = "https://test-api.data.altinn.no/v1/public/ccr";
-    private const string CrrBaseProd = "https://api.data.altinn.no/v1/public/ccr";
+    private const string CcrBaseDev = "https://dev-api.data.altinn.no/v1/public/ccr";
+    private const string CcrBaseTest = "https://test-api.data.altinn.no/v1/public/ccr";
+    private const string CcrBaseProd = "https://api.data.altinn.no/v1/public/ccr";
@@
-            "dev" => CrrBaseDev,
-            "test" => CrrBaseTest,
-            "prod" => CrrBaseProd,
+            "dev" => CcrBaseDev,
+            "test" => CcrBaseTest,
+            "prod" => CcrBaseProd,

Also applies to: 191-207


69-143: DRY: synthetic guard repeated in 5 methods.

Extract guard to a small helper to reduce duplication and drift.

Example:

private bool BlockedSynthetic(string org) => IsSyntheticOrganizationNumber(org) && !AllowTestCcrLookup;

Then early-return where applicable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddc3808 and e2e6049.

📒 Files selected for processing (37)
  • Dan.Common.UnitTest/Dan.Common.UnitTest.csproj (2 hunks)
  • Dan.Common/Dan.Common.csproj (1 hunks)
  • Dan.Common/Exceptions/CcrException.cs (1 hunks)
  • Dan.Common/Extensions/HostBuilderExtensions.cs (1 hunks)
  • Dan.Common/Interfaces/IEntityRegistryApiClientService.cs (1 hunks)
  • Dan.Common/Interfaces/IEntityRegistryService.cs (1 hunks)
  • Dan.Common/Models/BrregPage.cs (1 hunks)
  • Dan.Common/Models/EntityRegistryUnit.cs (1 hunks)
  • Dan.Common/Models/EntityRegistryUnitHierarchy.cs (1 hunks)
  • Dan.Common/Services/CcrClientService.cs (1 hunks)
  • Dan.Common/Services/DefaultEntityRegistryApiClientService.cs (1 hunks)
  • Dan.Common/Services/EntityRegistryService.cs (4 hunks)
  • Dan.Core.UnitTest/AuthorizationRequestValidatorServiceTest.cs (0 hunks)
  • Dan.Core.UnitTest/ConsentServiceTest.cs (0 hunks)
  • Dan.Core.UnitTest/Dan.Core.UnitTest.csproj (1 hunks)
  • Dan.Core.UnitTest/RequirementValidationServiceTest.cs (0 hunks)
  • Dan.Core.UnitTest/Services/EntityRegistryServiceTest.cs (2 hunks)
  • Dan.Core/Config/Settings.cs (1 hunks)
  • Dan.Core/Dan.Core.csproj (1 hunks)
  • Dan.Core/FuncCcrWrapper.cs (1 hunks)
  • Dan.Core/FuncConsentReceipt.cs (2 hunks)
  • Dan.Core/FuncOpenData.cs (1 hunks)
  • Dan.Core/FuncPpeProxy.cs (0 hunks)
  • Dan.Core/Middleware/ExceptionHandlerMiddleware.cs (1 hunks)
  • Dan.Core/Program.cs (2 hunks)
  • Dan.Core/Services/AltinnCorrespondenceService.cs (1 hunks)
  • Dan.Core/Services/AuthorizationRequestValidatorService.cs (2 hunks)
  • Dan.Core/Services/CachingEntityRegistryApiClientService.cs (4 hunks)
  • Dan.Core/Services/ConsentService.cs (2 hunks)
  • Dan.Core/Services/EntityRegistryService.cs (1 hunks)
  • Dan.Core/Services/Interfaces/IEntityRegistryApiClientService.cs (1 hunks)
  • Dan.Core/Services/Interfaces/IEntityRegistryService.cs (1 hunks)
  • Dan.Core/Services/RequirementValidationService.cs (2 hunks)
  • Dan.PluginTest/Config/PluginConstants.cs (1 hunks)
  • Dan.PluginTest/Dan.PluginTest.csproj (1 hunks)
  • Dan.PluginTest/Metadata.cs (1 hunks)
  • Dan.PluginTest/Plugin.cs (2 hunks)
💤 Files with no reviewable changes (4)
  • Dan.Core.UnitTest/ConsentServiceTest.cs
  • Dan.Core.UnitTest/AuthorizationRequestValidatorServiceTest.cs
  • Dan.Core.UnitTest/RequirementValidationServiceTest.cs
  • Dan.Core/FuncPpeProxy.cs
🧰 Additional context used
🧬 Code graph analysis (17)
Dan.Common/Services/DefaultEntityRegistryApiClientService.cs (1)
Dan.Common/Services/EntityRegistryService.cs (1)
  • Obsolete (10-299)
Dan.Common/Extensions/HostBuilderExtensions.cs (1)
Dan.Common/Services/CcrClientService.cs (1)
  • CcrClientService (56-208)
Dan.Common/Exceptions/CcrException.cs (1)
Dan.Core/Helpers/EvidenceSourceHelper.cs (1)
  • Exception (226-229)
Dan.Core/Program.cs (3)
Dan.Core/Services/EntityRegistryService.cs (1)
  • EntityRegistryService (7-280)
Dan.Core/Services/CachingEntityRegistryApiClientService.cs (1)
  • CachingEntityRegistryApiClientService (11-154)
Dan.Common/Models/EntityRegistryUnit.cs (1)
  • EntityRegistryUnit (8-100)
Dan.Common/Interfaces/IEntityRegistryService.cs (2)
Dan.Common/Services/DefaultEntityRegistryApiClientService.cs (1)
  • Obsolete (8-36)
Dan.Common/Services/EntityRegistryService.cs (1)
  • Obsolete (10-299)
Dan.Core/Services/Interfaces/IEntityRegistryApiClientService.cs (1)
Dan.Common/Interfaces/IEntityRegistryApiClientService.cs (1)
  • Task (12-12)
Dan.Core/Services/Interfaces/IEntityRegistryService.cs (5)
Dan.Common/Services/EntityRegistryService.cs (3)
  • SimpleEntityRegistryUnit (232-251)
  • IsPublicAgency (179-185)
  • IsPublicAgency (190-190)
Dan.Common/Models/SimpleEntityRegistryUnit.cs (1)
  • SimpleEntityRegistryUnit (4-13)
Dan.Common/Models/EntityRegistryUnit.cs (1)
  • EntityRegistryUnit (8-100)
Dan.Common/Models/EntityRegistryUnitHierarchy.cs (1)
  • EntityRegistryUnitHierarchy (3-10)
Dan.Common/Interfaces/IEntityRegistryService.cs (2)
  • IsPublicAgency (97-97)
  • IsPublicAgency (104-104)
Dan.Common/Models/EntityRegistryUnitHierarchy.cs (2)
Dan.Common/Models/EntityRegistryUnit.cs (1)
  • EntityRegistryUnit (8-100)
Dan.Common/Models/BrregPage.cs (1)
  • Subunits (32-39)
Dan.PluginTest/Plugin.cs (3)
Dan.Core/FuncCcrWrapper.cs (5)
  • Function (12-29)
  • Function (31-42)
  • Function (44-61)
  • Function (63-74)
  • Function (76-92)
Dan.PluginTest/Config/PluginConstants.cs (1)
  • PluginConstants (3-17)
Dan.Common/Util/EvidenceSourceResponse.cs (1)
  • EvidenceSourceResponse (9-112)
Dan.PluginTest/Metadata.cs (2)
Dan.PluginTest/Config/PluginConstants.cs (1)
  • PluginConstants (3-17)
Dan.Common/Models/EvidenceValue.cs (1)
  • SchemaFromObject (79-82)
Dan.Common/Models/BrregPage.cs (1)
Dan.Common/Models/EntityRegistryUnit.cs (2)
  • Links (147-154)
  • EntityRegistryUnit (8-100)
Dan.Core/Services/CachingEntityRegistryApiClientService.cs (5)
Dan.Common/Models/EntityRegistryUnit.cs (4)
  • Organisasjonsform (162-172)
  • Links (147-154)
  • Link (156-160)
  • EntityRegistryUnit (8-100)
Dan.Common/Services/EntityRegistryService.cs (3)
  • Uri (253-266)
  • Uri (268-281)
  • Uri (283-298)
Dan.Common/Services/DefaultEntityRegistryApiClientService.cs (1)
  • Task (24-35)
Dan.Core/Config/Settings.cs (1)
  • Settings (11-400)
Dan.Common/Models/BrregPage.cs (2)
  • BrregPage (8-27)
  • Subunits (32-39)
Dan.Common/Interfaces/IEntityRegistryApiClientService.cs (2)
Dan.Common/Services/DefaultEntityRegistryApiClientService.cs (1)
  • Obsolete (8-36)
Dan.Common/Services/EntityRegistryService.cs (1)
  • Obsolete (10-299)
Dan.Core/FuncCcrWrapper.cs (3)
Dan.Common/Interfaces/IEntityRegistryService.cs (9)
  • Task (27-27)
  • Task (34-34)
  • Task (44-44)
  • Task (51-51)
  • Task (72-72)
  • Task (90-90)
  • Task (111-111)
  • IsPublicAgency (97-97)
  • IsPublicAgency (104-104)
Dan.Common/Services/EntityRegistryService.cs (2)
  • IsPublicAgency (179-185)
  • IsPublicAgency (190-190)
Dan.Core/Services/EntityRegistryService.cs (1)
  • IsPublicAgency (187-193)
Dan.Common/Services/CcrClientService.cs (4)
Dan.Common/Models/EntityRegistryUnit.cs (1)
  • EntityRegistryUnit (8-100)
Dan.Common/Models/EntityRegistryUnitHierarchy.cs (1)
  • EntityRegistryUnitHierarchy (3-10)
Dan.Common/Constants.cs (1)
  • Constants (7-33)
Dan.Common/Exceptions/CcrException.cs (4)
  • CcrException (6-34)
  • CcrException (12-14)
  • CcrException (21-23)
  • CcrException (31-33)
Dan.Core/Services/EntityRegistryService.cs (5)
Dan.Common/Models/EntityRegistryUnit.cs (2)
  • EntityRegistryUnit (8-100)
  • Organisasjonsform (162-172)
Dan.Core/Services/Interfaces/IEntityRegistryService.cs (6)
  • Task (23-23)
  • Task (33-33)
  • Task (40-40)
  • Task (47-47)
  • Task (57-61)
  • Task (68-68)
Dan.Common/Services/EntityRegistryService.cs (7)
  • SimpleEntityRegistryUnit (232-251)
  • IsSyntheticOrganizationNumber (201-204)
  • IsPublicAgency (179-185)
  • IsPublicAgency (190-190)
  • Uri (253-266)
  • Uri (268-281)
  • Uri (283-298)
Dan.Common/Models/SimpleEntityRegistryUnit.cs (1)
  • SimpleEntityRegistryUnit (4-13)
Dan.Common/Services/CcrClientService.cs (1)
  • IsSyntheticOrganizationNumber (186-189)
Dan.Core.UnitTest/Services/EntityRegistryServiceTest.cs (3)
Dan.Core/Services/EntityRegistryService.cs (5)
  • EntityRegistryService (7-280)
  • Uri (252-259)
  • Uri (261-268)
  • Uri (270-279)
  • IsPublicAgency (187-193)
Dan.Common/Interfaces/IEntityRegistryApiClientService.cs (1)
  • Task (12-12)
Dan.Common/Models/EntityRegistryUnit.cs (4)
  • EntityRegistryUnit (8-100)
  • Organisasjonsform (162-172)
  • SektorKodeDto (138-145)
  • NaeringsKodeDto (126-136)
🪛 GitHub Check: build-and-test
Dan.Common/Models/EntityRegistryUnitHierarchy.cs

[warning] 7-7:
Missing XML comment for publicly visible type or member 'EntityRegistryUnitHierarchy.Unit'


[warning] 5-5:
Missing XML comment for publicly visible type or member 'EntityRegistryUnitHierarchy.OrgNumber'


[warning] 3-3:
Missing XML comment for publicly visible type or member 'EntityRegistryUnitHierarchy'


[warning] 9-9:
Non-nullable property 'Subunits' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.


[warning] 7-7:
Non-nullable property 'Unit' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.


[warning] 5-5:
Non-nullable property 'OrgNumber' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (23)
Dan.Common.UnitTest/Dan.Common.UnitTest.csproj (2)

12-20: Test framework updates are straightforward; approve dependency versions.

The incremental updates to test-related packages (FluentAssertions, MSTest, coverlet) are all minor or patch versions and represent standard maintenance.


27-29: Verify the new Services folder structure is properly wired.

The addition of <Folder Include="Services\" /> aligns with the CCR-related service reorganization mentioned in the PR summary. Ensure that test files in this folder are discoverable and the folder structure is properly reflected in your local development environment.

Dan.Common/Dan.Common.csproj (2)

37-38: ⚠️ Verify code compatibility with Azure Functions v2.0+ MAJOR version updates.

The updates to Microsoft.Azure.Functions.Worker (1.23.0 → 2.1.0) and Microsoft.Azure.Functions.Worker.ApplicationInsights (1.3.0 → 2.0.0) are MAJOR version bumps that introduce breaking changes. Ensure that:

  1. Function initialization, configuration, and dependency injection code is updated for v2.x patterns
  2. ApplicationInsights integration is compatible with v2.0.0
  3. Host builder and worker setup follows new conventions

Please verify that all code changes necessary to support these breaking changes are present in other files in this PR. Specifically:

  • Check if HostBuilderExtensions or function initialization code has been updated
  • Confirm logging/ApplicationInsights wiring is compatible with v2.0.0
  • Run the test suite to ensure no runtime errors occur with the new versions

39-41: Verify Microsoft.Extensions v9.0.9 compatibility across codebase.

The updates to Microsoft.Extensions.Caching.Memory, Microsoft.Extensions.Caching.StackExchangeRedis, and Microsoft.Extensions.Http.Polly from 8.0.x to 9.0.9 represent MAJOR version increments. Typical breaking changes in v9.x include API surface adjustments, configuration changes, and deprecation removals.

Confirm that:

  1. Caching configuration and usage patterns are compatible with v9.0.9
  2. Polly policy configuration/registration is compatible with v9.0.9
  3. The codebase uses only public APIs that remain in v9.x
Dan.Core.UnitTest/Dan.Core.UnitTest.csproj (1)

12-24: Dependency updates verified as compatible.

The upgrade of Microsoft.Extensions.Caching.Memory from 8.0.1 to 9.0.9 is safe for the net8.0 target. There are no package-specific API breaking changes between 8.0.1 and the 9.0.x series, and the NuGet package for the 9.x releases targets .NET 8.0, ensuring full compatibility. The changes include dependency updates and security improvements (CVE-2024-43483 fix). All other dependency bumps (MSTest packages, FluentAssertions, coverlet) are minor/patch versions. The FluentAssertions rationale (v8 subscription model) is well documented.

Dan.Core/Middleware/ExceptionHandlerMiddleware.cs (1)

109-110: No action required — error handling status codes are correctly maintained.

All instances of WriteAsJsonAsync throughout the codebase follow this pattern: the status code is set on the response object (via CreateResponse(statusCode) or the StatusCode property) before calling WriteAsJsonAsync(errorModel) without a parameter. This is the consistent, correct API usage for Azure Functions Worker 2.1.0. The status codes remain properly configured regardless of the parameter removal, so error responses will continue to return 401, 404, 500, 503, etc., as intended.

Dan.Core/Config/Settings.cs (1)

363-367: LGTM!

The new CcrUrl property follows the established pattern for configuration settings and is properly documented.

Dan.Common/Interfaces/IEntityRegistryService.cs (1)

6-6: LGTM!

The deprecation is clearly communicated with a migration path to ICcrClientService.

Dan.Common/Exceptions/CcrException.cs (1)

1-34: LGTM!

The exception class follows standard exception patterns with proper constructor chaining and clear documentation.

Dan.Common/Services/DefaultEntityRegistryApiClientService.cs (1)

8-8: LGTM!

The deprecation aligns with the broader migration to ICcrClientService and provides clear guidance.

Dan.Common/Extensions/HostBuilderExtensions.cs (1)

118-118: LGTM!

The DI registration for ICcrClientService follows the established pattern and uses an appropriate transient lifetime.

Dan.PluginTest/Dan.PluginTest.csproj (1)

11-12: LGTM!

The package updates are minor/patch versions that align with the broader dependency updates in Dan.Core.

Dan.Common/Models/EntityRegistryUnit.cs (1)

53-55: LGTM!

The new Underenheter property follows the established pattern and properly maps the nested sub-units data from CCR responses.

Dan.Core/Dan.Core.csproj (1)

36-68: Review comment is not supported by evidence.

The codebase uses Polly through backward-compatible patterns (PolicyRegistry, Policy.CacheAsync), which remain stable in v8.6.3. The web search confirms Microsoft.Extensions.Caching 9.0.9 introduces no breaking changes to the IDistributedCache interface used throughout the code. Version updates are compatible with existing usage patterns.

Dan.PluginTest/Config/PluginConstants.cs (1)

16-16: LGTM!

The new constant follows the existing naming pattern and is properly integrated with the metadata and plugin test flow.

Dan.Common/Interfaces/IEntityRegistryApiClientService.cs (1)

6-6: LGTM! Deprecation properly signals the migration path.

The Obsolete attribute correctly guides consumers to use ICcrClientService instead.

Dan.PluginTest/Metadata.cs (1)

96-111: LGTM!

The new evidence code entry is properly structured and consistent with the existing DatasetOne entry pattern.

Dan.Core/FuncConsentReceipt.cs (1)

39-51: Verify removal of UseCoreProxy configuration.

Similar to AltinnCorrespondenceService, the initialization _entityRegistryService.UseCoreProxy = false; was removed. Ensure this is intentional and the default behavior is correct for this service.

Dan.Core/Program.cs (2)

138-140: LGTM! Good use of explicit namespacing during migration.

The comment clearly documents the temporary nature of the explicit namespacing, which helps prevent confusion during the transition period.


172-177: LGTM! Cache policy properly configured.

The new EntityRegistryListCachePolicy is correctly configured with appropriate serialization and 12-hour expiration, matching the pattern used for single entity caching.

Dan.Core/FuncOpenData.cs (1)

23-24: Interface type change: OK; confirm DI

Type update is consistent. Ensure DI resolves Services.Interfaces.IEntityRegistryService in this project.

Use the DI verification script from earlier comments to confirm registration.

Also applies to: 27-32

Dan.Core/Services/CachingEntityRegistryApiClientService.cs (1)

18-18: Remove review comment; constant is actively used in cache policy registration

The EntityRegistryListCachePolicy constant is used in Dan.Core/Program.cs:173 where it's passed to Policy.CacheAsync() as part of the cache policy registration setup. This is the intended usage location, confirming the constant is not dead code.

Likely an incorrect or invalid review comment.

Dan.Common/Services/EntityRegistryService.cs (1)

10-13: No configuration issues found — C# 12 features are properly supported.

The repository targets net8.0 across all projects, which defaults to C# 12 language version. No LangVersion restrictions exist globally or per-project. The primary constructor syntax (line 11-12) and collection expressions (lines 44, 45, 49) are valid C# 12 features that are correctly supported in this configuration.

Comment thread Dan.Common/Models/EntityRegistryUnitHierarchy.cs
Comment thread Dan.Common/Services/CcrClientService.cs
Comment thread Dan.Common/Services/EntityRegistryService.cs Outdated
Comment thread Dan.Common/Services/EntityRegistryService.cs Outdated
Comment thread Dan.Core/Services/AltinnCorrespondenceService.cs Outdated
Comment thread Dan.Core/Services/CachingEntityRegistryApiClientService.cs
Comment thread Dan.Core/Services/ConsentService.cs
Comment thread Dan.Core/Services/EntityRegistryService.cs
Comment thread Dan.Core/Services/EntityRegistryService.cs Outdated
Comment thread Dan.PluginTest/Plugin.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
Dan.Common/Services/EntityRegistryService.cs (1)

51-51: Remove unused SubunitListCache declaration.

This cache is declared but never read or written in this file. The actual cache implementation exists in Dan.Core/Services/EntityRegistryService.cs. Remove this unused declaration to eliminate dead code.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2e6049 and 7022cd4.

📒 Files selected for processing (10)
  • Dan.Common/GlobalUsings.cs (1 hunks)
  • Dan.Common/Models/EntityRegistryUnitHierarchy.cs (1 hunks)
  • Dan.Common/Services/EntityRegistryService.cs (3 hunks)
  • Dan.Core/FuncConsentReceipt.cs (2 hunks)
  • Dan.Core/Services/AltinnCorrespondenceService.cs (1 hunks)
  • Dan.Core/Services/AuthorizationRequestValidatorService.cs (2 hunks)
  • Dan.Core/Services/CachingEntityRegistryApiClientService.cs (4 hunks)
  • Dan.Core/Services/ConsentService.cs (2 hunks)
  • Dan.Core/Services/EntityRegistryService.cs (1 hunks)
  • Dan.Core/Services/RequirementValidationService.cs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Dan.Core/Services/AuthorizationRequestValidatorService.cs
  • Dan.Common/Models/EntityRegistryUnitHierarchy.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-29T09:05:48.800Z
Learnt from: SondreJDigdir
PR: data-altinn-no/core#196
File: Dan.Core/Services/AltinnCorrespondenceService.cs:57-65
Timestamp: 2025-10-29T09:05:48.800Z
Learning: The IEntityRegistryService interface has been split into two separate implementations: Dan.Core.Services.Interfaces.IEntityRegistryService (which only uses direct endpoints and does not have a UseCoreProxy property) and Dan.Common.Interfaces.IEntityRegistryService (which supports proxy routing with UseCoreProxy). When reviewing Dan.Core services, the absence of UseCoreProxy configuration is intentional and correct.

Applied to files:

  • Dan.Core/FuncConsentReceipt.cs
  • Dan.Core/Services/CachingEntityRegistryApiClientService.cs
  • Dan.Core/Services/AltinnCorrespondenceService.cs
  • Dan.Core/Services/EntityRegistryService.cs
  • Dan.Core/Services/ConsentService.cs
  • Dan.Core/Services/RequirementValidationService.cs
  • Dan.Common/Services/EntityRegistryService.cs
🧬 Code graph analysis (3)
Dan.Core/Services/CachingEntityRegistryApiClientService.cs (4)
Dan.Common/Models/EntityRegistryUnit.cs (4)
  • Organisasjonsform (162-172)
  • Links (147-154)
  • Link (156-160)
  • EntityRegistryUnit (8-100)
Dan.Common/Services/DefaultEntityRegistryApiClientService.cs (1)
  • Task (24-35)
Dan.Core/Config/Settings.cs (1)
  • Settings (11-400)
Dan.Common/Models/BrregPage.cs (2)
  • BrregPage (8-27)
  • Subunits (32-39)
Dan.Core/Services/EntityRegistryService.cs (5)
Dan.Core/Config/Settings.cs (1)
  • Settings (11-400)
Dan.Common/Models/EntityRegistryUnit.cs (2)
  • EntityRegistryUnit (8-100)
  • Organisasjonsform (162-172)
Dan.Common/Services/EntityRegistryService.cs (2)
  • SimpleEntityRegistryUnit (231-250)
  • IsSyntheticOrganizationNumber (200-203)
Dan.Common/Models/SimpleEntityRegistryUnit.cs (1)
  • SimpleEntityRegistryUnit (4-13)
Dan.Common/Services/CcrClientService.cs (1)
  • IsSyntheticOrganizationNumber (186-189)
Dan.Common/Services/EntityRegistryService.cs (4)
Dan.Common/Services/DefaultEntityRegistryApiClientService.cs (1)
  • Obsolete (8-36)
Dan.Core/Services/EntityRegistryService.cs (14)
  • EntityRegistryService (8-294)
  • Task (43-44)
  • Task (50-51)
  • Task (56-104)
  • Task (109-123)
  • Task (133-170)
  • Task (175-175)
  • Task (180-184)
  • Task (202-221)
  • Task (223-226)
  • Task (228-231)
  • Uri (266-273)
  • Uri (275-282)
  • Uri (284-293)
Dan.Core/Services/CachingEntityRegistryApiClientService.cs (4)
  • Task (23-56)
  • Task (58-110)
  • Task (117-136)
  • Task (138-158)
Dan.Common/Interfaces/IEntityRegistryService.cs (2)
  • Task (27-27)
  • Task (34-34)
🪛 GitHub Actions: Build and test a PR
Dan.Core/Services/EntityRegistryService.cs

[error] 199-199: TestSyntheticLookupsNotAllowedByDefault failed due to NullReferenceException in IsSyntheticOrganizationNumber. Investigate possible null organizationNumber or uninitialized state leading to a null reference.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (11)
Dan.Common/GlobalUsings.cs (1)

9-9: LGTM! Global using for System.Net.Http added.

This addition supports the new CCR client service infrastructure that uses HttpClientFactory.

Dan.Core/Services/RequirementValidationService.cs (1)

20-20: Interface migration to Interfaces.IEntityRegistryService is correct.

The change from IEntityRegistryService to Interfaces.IEntityRegistryService aligns with the architectural split where Dan.Core services use direct endpoints only. The removal of UseCoreProxy and AllowTestCcrLookup initialization is intentional and correct for this interface.

Based on learnings

Also applies to: 34-40

Dan.Core/Services/CachingEntityRegistryApiClientService.cs (3)

11-21: Well-structured refactor to distributed caching with keyed locking.

The addition of IDistributedCache and AsyncKeyedLocker ensures thread-safe caching and prevents duplicate concurrent requests for the same entity. The dependency injection via primary constructor is clean.


51-53: Good fix: differentiated TTL for null vs non-null results.

Caching null results for only 5 minutes (vs 12 hours for valid results) prevents long-term masking of newly created units. This addresses the past review concern.


60-68: Safe regex extraction with match guard.

The code now checks match.Success before accessing Groups[1], preventing potential exceptions. Returning an empty list when the pattern doesn't match is a reasonable fallback.
[approve_code_challenges]

Dan.Common/Services/EntityRegistryService.cs (1)

10-12: Obsolete marking and primary constructor migration look good.

The deprecation message correctly directs users to Dan.Common.Services.CcrClientService as the replacement.

Dan.Core/Services/EntityRegistryService.cs (2)

8-16: Constructor and default AllowTestCcrLookup configuration look good.

The primary constructor pattern is clean, and defaulting AllowTestCcrLookup to !Settings.IsProductionEnvironment is reasonable for a non-production safety check.


247-261: Defensive initialization of IndustrialCodes is well-implemented.

The null-coalescing assignment (??= []) ensures the list is initialized before adding Naeringskode2 or Naeringskode3, preventing null reference exceptions. The comment acknowledges the defensive approach.

Dan.Core/FuncConsentReceipt.cs (1)

25-25: Interface migration is consistent with architectural changes.

The update to Services.Interfaces.IEntityRegistryService and removal of UseCoreProxy initialization aligns with the Dan.Core interface split. Dan.Core services use direct endpoints only.

Based on learnings

Also applies to: 39-45

Dan.Core/Services/ConsentService.cs (1)

36-36: Interface migration completed correctly.

The update to Interfaces.IEntityRegistryService removes the need for UseCoreProxy and AllowTestCcrLookup initialization. Per learnings, the Dan.Core interface only uses direct endpoints.

Based on learnings

Note: A past review flagged potential singleton state bleed if AllowTestCcrLookup is mutated across multiple locations. Verify the DI lifetime and consider immutable configuration via constructor options.

Also applies to: 50-64

Dan.Core/Services/AltinnCorrespondenceService.cs (1)

55-55: Interface update aligns with architectural split.

The migration to Interfaces.IEntityRegistryService and removal of UseCoreProxy/AllowTestCcrLookup initialization is intentional. The Dan.Core interface implementation uses direct endpoints only, making the proxy configuration unnecessary.

Based on learnings

Also applies to: 57-62

Comment thread Dan.Core/Services/EntityRegistryService.cs
Comment thread Dan.Core/Services/EntityRegistryService.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
Dan.Core/Services/EntityRegistryService.cs (1)

92-101: Preserve last known parent if an upstream parent fetch fails

If InternalGet returns null once, you overwrite parentUnit and end up returning null even though a valid subunit was found. Keep the last successful parent.

-        var parentUnit = unit;
-        do
-        {
-            // Only subunits are at the leaf node, any nested parents are MainUnits
-            // Example: https://data.brreg.no/enhetsregisteret/api/underenheter/879587662
-            parentUnit = await InternalGet(parentUnit.OverordnetEnhet!, UnitType.MainUnit);
-        }
-        while (parentUnit?.OverordnetEnhet != null);
-        unit = parentUnit;
+        var parentUnit = unit;
+        var parentNumber = parentUnit.OverordnetEnhet;
+        while (!string.IsNullOrEmpty(parentNumber))
+        {
+            var next = await InternalGet(parentNumber, UnitType.MainUnit);
+            if (next is null) break; // upstream inconsistency; keep last known
+            parentUnit = next;
+            parentNumber = next.OverordnetEnhet;
+        }
+        unit = parentUnit;
🧹 Nitpick comments (7)
Dan.Core/Services/EntityRegistryService.cs (3)

29-33: Static dictionaries can grow unbounded

Entries never get removed; expired ones persist and keys accumulate. Prefer IMemoryCache with TTL/size limits, or actively evict expired entries on read.

  • Option A: replace with IMemoryCache, set absolute expiration 10m and a size limit.
  • Option B: on cache hit with expired entry, remove with TryRemove to bound growth.
  • Option C: wrap access behind a small cache helper to dedupe in-flight fetches (Lazy<Task>).

139-170: Add cycle guard and cancellation to recursive hierarchy

Defensive: guard against accidental cycles and accept a CancellationToken to abort deep trees; current default depth 10 helps but CT is preferable.


286-299: Make query URL construction robust and encoded

Current string.Format + “? …” works after the Replace, but it’s brittle and unencoded. Use UriBuilder and EscapeDataString; don’t rely on formatting placeholders.

-        if(urlPattern.EndsWith("/{0}"))
-        {
-            urlPattern = urlPattern.Replace("/{0}", "{0}");
-        }
-        var query = $"?overordnetEnhet={organizationNumber}";
-
-        return new Uri(string.Format(urlPattern, query));
+        var baseUrl = urlPattern.Replace("/{0}", string.Empty).Replace("{0}", string.Empty);
+        var builder = new UriBuilder(baseUrl)
+        {
+            Query = $"overordnetEnhet={Uri.EscapeDataString(organizationNumber)}"
+        };
+        return builder.Uri;
Dan.Core.UnitTest/Services/EntityRegistryServiceTest.cs (4)

26-58: Tighten URL matching in mocks

Contains(...) can misroute (e.g., “/enheter/91” also matches “/enheter/910”). Prefer EndsWith with Ordinal.

-                if (url.AbsolutePath.Contains("/enheter/91"))
+                if (url.AbsolutePath.EndsWith("/enheter/91", StringComparison.Ordinal))
                 {
                     return Task.FromResult(new EntityRegistryUnit { Organisasjonsnummer = "91", Organisasjonsform = orgForm })!;
                 }
-                if (url.AbsolutePath.Contains("/underenheter/92"))
+                if (url.AbsolutePath.EndsWith("/underenheter/92", StringComparison.Ordinal))
                 {
                     return Task.FromResult(new EntityRegistryUnit { Organisasjonsnummer = "92", OverordnetEnhet = "91", Organisasjonsform = orgForm  })!;
                 }
-                if (url.AbsolutePath.Contains("/enheter/93"))
+                if (url.AbsolutePath.EndsWith("/enheter/93", StringComparison.Ordinal))
                 {
                     return Task.FromResult(new EntityRegistryUnit { Organisasjonsnummer = "93", OverordnetEnhet = "91", Organisasjonsform = orgForm  })!;
                 }
-                if (url.AbsolutePath.Contains("/underenheter/94"))
+                if (url.AbsolutePath.EndsWith("/underenheter/94", StringComparison.Ordinal))
                 {
                     return Task.FromResult(new EntityRegistryUnit { Organisasjonsnummer = "94", OverordnetEnhet = "93", Organisasjonsform = orgForm  })!;
                 }
-                if (url.AbsolutePath.Contains("/underenheter/31"))
+                if (url.AbsolutePath.EndsWith("/underenheter/31", StringComparison.Ordinal))
                 {
                     return Task.FromResult(new EntityRegistryUnit { Organisasjonsnummer = "31", Organisasjonsform = orgForm  })!;
                 }

61-131: Use async/await in tests to avoid .Result deadlocks

Modernize tests to async Task and await service calls. Repeat this pattern across test methods.

-    public void TestGetMapping()
+    public async Task TestGetMapping()
     {
         var s = new EntityRegistryService(entityRegistryApiClientServiceMock.Object, logger.Object);
-        var r = s.Get("91").Result;
+        var r = await s.Get("91");
         Assert.AreEqual("91", r?.OrganizationNumber);
     }

14-21: Test instance reuse

You already construct entityRegistryService in the fixture; consider reusing it in all tests for consistency unless isolation is required.


133-164: Add coverage for subunit listing and hierarchy

Consider tests for GetSubunits and GetSubunitHierarchy (with maxDepth/cycle guard) to validate the new endpoint/query path end-to-end. I can draft test cases if helpful.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7022cd4 and bbfbc8a.

📒 Files selected for processing (2)
  • Dan.Core.UnitTest/Services/EntityRegistryServiceTest.cs (1 hunks)
  • Dan.Core/Services/EntityRegistryService.cs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-29T09:05:48.800Z
Learnt from: SondreJDigdir
PR: data-altinn-no/core#196
File: Dan.Core/Services/AltinnCorrespondenceService.cs:57-65
Timestamp: 2025-10-29T09:05:48.800Z
Learning: The IEntityRegistryService interface has been split into two separate implementations: Dan.Core.Services.Interfaces.IEntityRegistryService (which only uses direct endpoints and does not have a UseCoreProxy property) and Dan.Common.Interfaces.IEntityRegistryService (which supports proxy routing with UseCoreProxy). When reviewing Dan.Core services, the absence of UseCoreProxy configuration is intentional and correct.

Applied to files:

  • Dan.Core.UnitTest/Services/EntityRegistryServiceTest.cs
  • Dan.Core/Services/EntityRegistryService.cs
🧬 Code graph analysis (2)
Dan.Core.UnitTest/Services/EntityRegistryServiceTest.cs (2)
Dan.Core/Services/EntityRegistryService.cs (15)
  • EntityRegistryService (8-300)
  • Uri (268-275)
  • Uri (277-284)
  • Uri (286-299)
  • Task (43-44)
  • Task (50-51)
  • Task (56-104)
  • Task (109-123)
  • Task (133-170)
  • Task (175-175)
  • Task (180-184)
  • Task (204-223)
  • Task (225-228)
  • Task (230-233)
  • IsPublicAgency (189-195)
Dan.Common/Models/EntityRegistryUnit.cs (4)
  • Organisasjonsform (162-172)
  • EntityRegistryUnit (8-100)
  • SektorKodeDto (138-145)
  • NaeringsKodeDto (126-136)
Dan.Core/Services/EntityRegistryService.cs (4)
Dan.Core/Config/Settings.cs (1)
  • Settings (11-400)
Dan.Common/Models/EntityRegistryUnit.cs (2)
  • EntityRegistryUnit (8-100)
  • Organisasjonsform (162-172)
Dan.Common/Services/EntityRegistryService.cs (4)
  • SimpleEntityRegistryUnit (231-250)
  • IsSyntheticOrganizationNumber (200-203)
  • Uri (252-265)
  • Uri (267-280)
Dan.Common/Models/SimpleEntityRegistryUnit.cs (1)
  • SimpleEntityRegistryUnit (4-13)
🔇 Additional comments (3)
Dan.Core/Services/EntityRegistryService.cs (3)

8-11: DI and interface split looks correct

Using Dan.Core.Services.Interfaces.IEntityRegistryService without UseCoreProxy is expected for Core. Good alignment with the split. Based on learnings


197-202: Null-safe and efficient synthetic check

Using StartsWith(char) and treating null/empty as synthetic avoids NREs and is efficient. LGTM.


249-263: No action required—C# 12 collection expressions are supported

The repository targets .NET 8 (net8.0) across all projects, which defaults to C# 12 language features. Collection expressions ([] and ??= []) will compile correctly on all agents without modification.

Comment thread Dan.Core/Services/EntityRegistryService.cs
Comment thread Dan.Core/Services/EntityRegistryService.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
Dan.Core/Services/EntityRegistryService.cs (4)

12-15: Doc now matches behavior

“Defaults to true outside Production” is clear and correct. Thanks for fixing.


196-201: Null‑safe synthetic org check — resolved

The added IsNullOrEmpty guard fixes the previous NRE. LGTM.


248-263: Defensive IndustrialCodes mapping — good

Null‑coalescing initialization avoids NREs when only Naeringskode2/3 exist.


91-101: Parent climb uses null‑forgiving — acceptable per business rules

Given the control flow guarantees a subunit at entry, dereferencing OverordnetEnhet is safe here.

🧹 Nitpick comments (5)
Dan.Core/Services/EntityRegistryService.cs (5)

55-63: Trim and short‑circuit empty org numbers before any lookups

Avoid building “/enheter/” URLs and polluted cache keys in non‑prod. Normalize once up front.

     public async Task<EntityRegistryUnit?> GetFull(string organizationNumber, bool attemptSubUnitLookupIfNotFound = true,
         bool nestToAndReturnMainUnit = false, bool subUnitOnly = false)
     {
-
-        if (IsSyntheticOrganizationNumber(organizationNumber) && !AllowTestCcrLookup)
+        if (string.IsNullOrWhiteSpace(organizationNumber))
+        {
+            return null;
+        }
+        organizationNumber = organizationNumber.Trim();
+        if (IsSyntheticOrganizationNumber(organizationNumber) && !AllowTestCcrLookup)
         {
             return null;
         }

108-122: Guard and normalize input in GetSubunits as well

Same concern as GetFull; also prevents cache entries like “SubunitList_ ”.

     public async Task<List<EntityRegistryUnit>> GetSubunits(string organizationNumber)
     {
+        if (string.IsNullOrWhiteSpace(organizationNumber))
+        {
+            return new List<EntityRegistryUnit>(capacity: 0);
+        }
+        organizationNumber = organizationNumber.Trim();
         var cacheKey = "SubunitList_" + organizationNumber;
         if (SubunitListCache.TryGetValue(cacheKey, out var cacheEntry) && cacheEntry.expiresAt > DateTime.UtcNow)
         {
             return cacheEntry.list;
         }

111-114: Evict expired cache entries to avoid unbounded growth

Dictionaries keep expired keys forever. Remove them when encountered.

-        if (SubunitListCache.TryGetValue(cacheKey, out var cacheEntry) && cacheEntry.expiresAt > DateTime.UtcNow)
-        {
-            return cacheEntry.list;
-        }
+        if (SubunitListCache.TryGetValue(cacheKey, out var cacheEntry))
+        {
+            if (cacheEntry.expiresAt > DateTime.UtcNow) return cacheEntry.list;
+            SubunitListCache.TryRemove(cacheKey, out _);
+        }
-        if (EntityRegistryUnitsCache.TryGetValue(cacheKey, out var cacheEntry) && cacheEntry.expiresAt > DateTime.UtcNow)
-        {
-            return cacheEntry.unit;
-        }
+        if (EntityRegistryUnitsCache.TryGetValue(cacheKey, out var cacheEntry))
+        {
+            if (cacheEntry.expiresAt > DateTime.UtcNow) return cacheEntry.unit;
+            EntityRegistryUnitsCache.TryRemove(cacheKey, out _);
+        }

Also applies to: 206-209


147-159: Avoid fetching subunits when depth limit reached

Move the network call inside the depth check.

-        var subunits = await GetSubunits(orgNumber);
         var subunitHierarchies = new List<EntityRegistryUnitHierarchy>();
         if (currentDepth < maxDepth)
         {
-            foreach (var subunit in subunits)
+            var subunits = await GetSubunits(orgNumber);
+            foreach (var subunit in subunits)
             {
                 var subunitHierarchy = await GetSubunitHierarchy(subunit.Organisasjonsnummer, currentDepth, maxDepth, subunit);
                 if (subunitHierarchy != null)
                 {
                     subunitHierarchies.Add(subunitHierarchy);
                 }
             } 
         }

285-298: Query URL fix looks good; consider UriBuilder for robustness

Replacing “/{0}” and formatting with “?overordnetEnhet=…” works. Using UriBuilder avoids string surgery and handles encoding reliably.

-        if(urlPattern.EndsWith("/{0}"))
-        {
-            urlPattern = urlPattern.Replace("/{0}", "{0}");
-        }
-        var query = $"?overordnetEnhet={organizationNumber}";
-
-        return new Uri(string.Format(urlPattern, query));
+        var baseUrl = urlPattern.Replace("/{0}", string.Empty).Replace("{0}", string.Empty);
+        var ub = new UriBuilder(baseUrl) { Query = $"overordnetEnhet={organizationNumber}" };
+        return ub.Uri;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbfbc8a and 5521d95.

📒 Files selected for processing (1)
  • Dan.Core/Services/EntityRegistryService.cs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-29T09:05:48.800Z
Learnt from: SondreJDigdir
PR: data-altinn-no/core#196
File: Dan.Core/Services/AltinnCorrespondenceService.cs:57-65
Timestamp: 2025-10-29T09:05:48.800Z
Learning: The IEntityRegistryService interface has been split into two separate implementations: Dan.Core.Services.Interfaces.IEntityRegistryService (which only uses direct endpoints and does not have a UseCoreProxy property) and Dan.Common.Interfaces.IEntityRegistryService (which supports proxy routing with UseCoreProxy). When reviewing Dan.Core services, the absence of UseCoreProxy configuration is intentional and correct.

Applied to files:

  • Dan.Core/Services/EntityRegistryService.cs
🧬 Code graph analysis (1)
Dan.Core/Services/EntityRegistryService.cs (7)
Dan.Core/Config/Settings.cs (1)
  • Settings (11-400)
Dan.Common/Models/EntityRegistryUnit.cs (2)
  • EntityRegistryUnit (8-100)
  • Organisasjonsform (162-172)
Dan.Common/Models/SimpleEntityRegistryUnit.cs (1)
  • SimpleEntityRegistryUnit (4-13)
Dan.Common/Services/CcrClientService.cs (1)
  • IsSyntheticOrganizationNumber (186-189)
Dan.Common/Models/EntityRegistryUnitHierarchy.cs (1)
  • EntityRegistryUnitHierarchy (6-22)
Dan.Common/Models/BrregPage.cs (1)
  • Subunits (32-39)
Dan.Common/Interfaces/IEntityRegistryService.cs (2)
  • IsPublicAgency (97-97)
  • IsPublicAgency (104-104)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (1)
Dan.Core/Services/EntityRegistryService.cs (1)

22-27: No action required — C# 12 collection expressions are properly supported

The codebase targets net8.0, which implicitly enables C# 12 and its collection expressions. No explicit LangVersion configuration is needed; the <TargetFramework>net8.0</TargetFramework> setting in Dan.Core.csproj automatically provides C# 12 support. The collection expressions used at lines 22-27 and 255-261 will compile successfully.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Dan.Core/Services/EntityRegistryService.cs (1)

136-173: Safe recursive hierarchy traversal with depth limit.

The maxDepth parameter (defaulting to 10) prevents runaway recursion. The depth tracking at lines 142-143 and the guard at line 153 ensure safe termination. Consider adding logging when hitting max depth for operational visibility.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5521d95 and 007046f.

📒 Files selected for processing (1)
  • Dan.Core/Services/EntityRegistryService.cs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-29T09:05:48.800Z
Learnt from: SondreJDigdir
PR: data-altinn-no/core#196
File: Dan.Core/Services/AltinnCorrespondenceService.cs:57-65
Timestamp: 2025-10-29T09:05:48.800Z
Learning: The IEntityRegistryService interface has been split into two separate implementations: Dan.Core.Services.Interfaces.IEntityRegistryService (which only uses direct endpoints and does not have a UseCoreProxy property) and Dan.Common.Interfaces.IEntityRegistryService (which supports proxy routing with UseCoreProxy). When reviewing Dan.Core services, the absence of UseCoreProxy configuration is intentional and correct.

Applied to files:

  • Dan.Core/Services/EntityRegistryService.cs
🧬 Code graph analysis (1)
Dan.Core/Services/EntityRegistryService.cs (3)
Dan.Core/Config/Settings.cs (1)
  • Settings (11-400)
Dan.Core/Services/CachingEntityRegistryApiClientService.cs (2)
  • Task (23-56)
  • Task (58-110)
Dan.Common/Models/EntityRegistryUnitHierarchy.cs (1)
  • EntityRegistryUnitHierarchy (6-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (5)
Dan.Core/Services/EntityRegistryService.cs (5)

8-31: Clean constructor and configuration.

The primary constructor syntax is concise and the AllowTestCcrLookup default correctly restricts synthetic lookups to non-production environments. Caching strategy with ConcurrentDictionary and TTL is appropriate for this registry access pattern.


58-66: Robust input validation.

The whitespace guard and trim at lines 58-62 prevent malformed URLs from empty or whitespace-only inputs, and the synthetic organization number check correctly enforces production restrictions.


200-205: Null-safe synthetic organization number check.

The string.IsNullOrEmpty guard prevents the NullReferenceException flagged in previous reviews when StartsWith is called on a null reference. The method now safely handles all inputs.


253-266: Null-safe industrial codes initialization.

The ??= [] pattern at lines 259 and 264 correctly addresses the previous NRE concern when Naeringskode2 or Naeringskode3 are present but Naeringskode1 is null. Each code is safely added to the list.


289-302: Query parameter construction now correct.

The logic at lines 295-298 properly strips the path placeholder before appending the query string, producing well-formed URLs like /underenheter?overordnetEnhet=... instead of the previously malformed /underenheter/?overordnetEnhet=....

Comment thread Dan.Core/Services/EntityRegistryService.cs
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