Skip to content

[RHCLOUD-44561] Add system endpoints on Endpoint Api V2#4056

Open
g-duval wants to merge 2 commits intoRedHatInsights:masterfrom
g-duval:RHCLOUD-44561_addSystemEndpointsOnEndpointApiV2
Open

[RHCLOUD-44561] Add system endpoints on Endpoint Api V2#4056
g-duval wants to merge 2 commits intoRedHatInsights:masterfrom
g-duval:RHCLOUD-44561_addSystemEndpointsOnEndpointApiV2

Conversation

@g-duval
Copy link
Copy Markdown
Contributor

@g-duval g-duval commented Jan 27, 2026

Why?

In order to be able to remove the Behavior Group management screen, we have to add system integration into regular integrations/endpoints apis.
Since end users will understand why they are notify even if they didn't configured such integration.
For UI purpose, those system integrations as to be flagged as read only.

Summary by Sourcery

Expose system (org-less) endpoints via the v2 endpoints API while keeping them hidden in v1, and label them as read-only in responses.

New Features:

  • Include system endpoints alongside regular endpoints in the v2 endpoints listing and single-endpoint retrieval APIs.
  • Expose a readOnly flag on endpoint DTOs to indicate system-managed integrations.

Enhancements:

  • Extend repository queries and counts to optionally include system endpoints in addition to org-scoped ones.
  • Adjust endpoint retrieval helpers to support including linked event types and system endpoints with a single shared implementation.
  • Remove obsolete endpoint test helper that asserted absence of integrations through the legacy /endpoints endpoint.

Tests:

  • Add coverage ensuring v1 endpoints APIs ignore system endpoints while v2 endpoints APIs return them and correctly set the readOnly flag.
  • Update repository tests to account for the new include-system-endpoints behavior in composite-type queries.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Jan 27, 2026

Reviewer's Guide

Adds support in the v2 endpoint API to include both regular and system endpoints, exposing a readOnly flag, while keeping v1 behavior unchanged; repository queries and tests are updated to handle optional inclusion of system integrations and their counts.

Sequence diagram for V2 getEndpoints including system integrations

sequenceDiagram
    actor User
    participant EndpointResourceV2
    participant EndpointResourceCommon
    participant EndpointRepository
    participant Database

    User ->> EndpointResourceV2: GET /api/notifications/v2.0/endpoints
    EndpointResourceV2 ->> EndpointResourceCommon: internalGetEndpoints(sec, query, targetType, activeOnly, name, true, true)

    EndpointResourceCommon ->> EndpointRepository: getEndpointsPerCompositeType(orgId, name, compositeType, activeOnly, query, true)
    EndpointRepository ->> Database: queryBuilderEndpointsPerType(orgId, name, type, activeOnly)
    Database -->> EndpointRepository: regular endpoints list

    EndpointRepository ->> Database: queryBuilderEndpointsPerType(null, name, type, activeOnly)
    Database -->> EndpointRepository: system endpoints list
    EndpointRepository -->> EndpointResourceCommon: combined endpoints list

    EndpointResourceCommon ->> EndpointRepository: getEndpointsCountPerCompositeType(orgId, name, compositeType, activeOnly, true)
    EndpointRepository ->> Database: count endpoints where orgId = orgId
    Database -->> EndpointRepository: regular count
    EndpointRepository ->> Database: count endpoints where orgId is null
    Database -->> EndpointRepository: system count
    EndpointRepository -->> EndpointResourceCommon: total count

    loop build EndpointDTO list
        EndpointResourceCommon ->> EndpointResourceCommon: fromEndpoint(endpoint)
        alt system endpoint
            EndpointResourceCommon ->> EndpointResourceCommon: endpointDTO.setReadOnly(true)
        else regular endpoint
            EndpointResourceCommon ->> EndpointResourceCommon: endpointDTO.setReadOnly(false)
        end
    end

    EndpointResourceCommon -->> EndpointResourceV2: EndpointPage
    EndpointResourceV2 -->> User: EndpointPage with readOnly flags
Loading

Sequence diagram for V2 getEndpoint including system integrations

sequenceDiagram
    actor User
    participant EndpointResourceV2
    participant EndpointResourceCommon
    participant EndpointRepository
    participant Database

    User ->> EndpointResourceV2: GET /api/notifications/v2.0/endpoints/{id}
    EndpointResourceV2 ->> EndpointResourceCommon: internalGetEndpoint(sec, id, true, true)

    EndpointResourceCommon ->> EndpointRepository: getEndpointWithLinkedEventTypes(orgId, id, true)
    EndpointRepository ->> Database: SELECT e LEFT JOIN FETCH eventTypes WHERE e.id = id AND (e.orgId = orgId OR e.orgId is null)
    Database -->> EndpointRepository: Endpoint or empty

    alt endpoint not found
        EndpointRepository -->> EndpointResourceCommon: Optional empty
        EndpointResourceCommon -->> EndpointResourceV2: throws NotFoundException
        EndpointResourceV2 -->> User: 404 Not Found
    else endpoint found
        EndpointRepository -->> EndpointResourceCommon: Optional with Endpoint
        EndpointResourceCommon ->> EndpointResourceCommon: fromEndpoint(endpoint)
        EndpointResourceCommon ->> EndpointResourceCommon: includeLinkedEventTypes(endpoint.eventTypes, endpointDTO)
        alt system endpoint
            EndpointResourceCommon ->> EndpointResourceCommon: endpointDTO.setReadOnly(true)
        else regular endpoint
            EndpointResourceCommon ->> EndpointResourceCommon: endpointDTO.setReadOnly(false)
        end
        EndpointResourceCommon -->> EndpointResourceV2: EndpointDTO
        EndpointResourceV2 -->> User: EndpointDTO with readOnly
    end
Loading

Class diagram for updated endpoint repository and resources

classDiagram
    class EndpointRepository {
        +Endpoint createEndpoint(Endpoint endpoint)
        +List~Endpoint~ getEndpointsPerCompositeType(String orgId, String name, Set~CompositeEndpointType~ type, Boolean activeOnly, Query limiter, boolean includeSystemIntegrations)
        +Long getEndpointsCountPerCompositeType(String orgId, String name, Set~CompositeEndpointType~ type, Boolean activeOnly, boolean includeSystemIntegrations)
        +Optional~Endpoint~ getSystemSubscriptionEndpoint(String orgId, SystemSubscriptionProperties properties, EndpointType endpointType)
        +Endpoint createSystemSubscriptionEndpoint(String accountId, String orgId, SystemSubscriptionProperties properties, EndpointType endpointType)
        +Optional~Endpoint~ getEndpointWithLinkedEventTypes(String orgId, UUID id, boolean includeSystemIntegrationFlag)
    }

    class EndpointResourceCommon {
        -EndpointRepository endpointRepository
        +EndpointDTO internalGetEndpoint(SecurityContext securityContext, UUID id, boolean includeLinkedEventTypes, boolean includeSystemIntegrationFlag)
        +EndpointPage internalGetEndpoints(SecurityContext sec, Query query, List~String~ targetType, Boolean activeOnly, String name, boolean includeLinkedEventTypes, boolean includeSystemIntegrations)
    }

    class EndpointResourceV2 {
        +EndpointDTO getEndpoint(SecurityContext sec, UUID id)
        +EndpointPage getEndpoints(SecurityContext sec, Query query, List~String~ targetType, Boolean activeOnly, String name)
    }

    class EndpointResource {
        +EndpointDTO getEndpoint(SecurityContext sec, UUID id)
        +EndpointPage getEndpoints(SecurityContext sec, Query query, List~String~ targetType, Boolean activeOnly, String name)
    }

    class EndpointDTO {
        +UUID id
        +String name
        +String description
        +Set~UUID~ eventTypes
        +Boolean readOnly
        +UUID getId()
        +void setId(UUID id)
        +String getName()
        +void setName(String name)
        +String getDescription()
        +void setDescription(String description)
        +Set~UUID~ getEventTypes()
        +void setEventTypes(Set~UUID~ eventTypes)
        +Boolean getReadOnly()
        +void setReadOnly(Boolean readOnly)
    }

    EndpointResourceCommon --> EndpointRepository : uses
    EndpointResourceV2 --> EndpointResourceCommon : delegates
    EndpointResource --> EndpointResourceCommon : delegates
    EndpointResourceV2 --> EndpointDTO : returns
    EndpointResource --> EndpointDTO : returns
    EndpointRepository --> Endpoint : manages
    EndpointDTO --> Endpoint : represents
Loading

File-Level Changes

Change Details Files
Expose readOnly flag on endpoints and populate it when including system integrations in v2 APIs while keeping v1 responses unchanged.
  • Add nullable readOnly Boolean field with getter/setter to EndpointDTO and include it in JSON only when non-null
  • In EndpointResourceCommon.internalGetEndpoint and internalGetEndpoints, accept a flag to control inclusion of system integrations and set readOnly based on whether orgId is null
  • Ensure v1 EndpointResource passes false for system integration inclusion while v2 EndpointResource passes true to include system endpoints and their readOnly state
backend/src/main/java/com/redhat/cloud/notifications/models/dto/v1/endpoint/EndpointDTO.java
backend/src/main/java/com/redhat/cloud/notifications/routers/handlers/endpoint/EndpointResourceCommon.java
backend/src/main/java/com/redhat/cloud/notifications/routers/handlers/endpoint/EndpointResource.java
backend/src/main/java/com/redhat/cloud/notifications/routers/handlers/endpoint/EndpointResourceV2.java
Extend repository queries to optionally include system integrations (orgId null) for list, count, and single-endpoint-with-event-types retrieval.
  • Update getEndpointsPerCompositeType to take includeSystemIntegrations flag and, when true, merge results of org-specific and org-null endpoints with same filtering, limit, and sort
  • Update getEndpointsCountPerCompositeType to take includeSystemIntegrations flag and, when true, add counts from org-null endpoints to the org-specific count
  • Change getEndpointWithLinkedEventTypes to take includeSystemIntegrationFlag and adjust JPQL to allow orgId null when flag is true
  • Adjust system-subscription related methods to call the updated repository methods with includeSystemIntegrations=false to preserve behavior
  • Update repository test to call the new getEndpointsPerCompositeType signature with includeSystemIntegrations=false
backend/src/main/java/com/redhat/cloud/notifications/db/repositories/EndpointRepository.java
backend/src/test/java/com/redhat/cloud/notifications/db/repositories/EndpointRepositoryTest.java
Add and adjust tests to cover v1 vs v2 behavior for system endpoints and readOnly flag, and to exercise new helper methods for v1 single-endpoint retrieval.
  • Add testCreateThenReadRegularAndSystemEndpoint verifying v1 ignores system endpoints and omits readOnly while v2 includes them and sets readOnly appropriately, in both single and paginated fetches
  • Update existing tests that verify endpoint listings with event types to assert readOnly is false in v2 and remains null in v1
  • Introduce fetchSingleEndpointV1 and fetchSingleEndpointV1Raw helper methods mirroring existing v2 helpers
  • Remove unused assertNoIntegrationsInDatabase helper that relied on the legacy /endpoints endpoint
backend/src/test/java/com/redhat/cloud/notifications/routers/handlers/endpoint/EndpointResourceTest.java

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The getEndpointsPerCompositeType/getEndpointsCountPerCompositeType changes build the final list and count by combining two separate queries (org-specific and system) after applying limit/sort, which will break pagination and global ordering (e.g. limit is effectively applied per query, and the combined list may not respect the requested sort); consider unifying this into a single query or applying limit/sort after combining results to keep paging semantics consistent.
  • When includeSystemIntegrations is true in getEndpointsPerCompositeType, system endpoints are appended after org-specific endpoints, which means they will always appear after all org-specific items regardless of sorting; if system and org endpoints should share the same global ordering, you may want to push the system inclusion into the query itself instead of merging in Java.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `getEndpointsPerCompositeType`/`getEndpointsCountPerCompositeType` changes build the final list and count by combining two separate queries (org-specific and system) after applying limit/sort, which will break pagination and global ordering (e.g. limit is effectively applied per query, and the combined list may not respect the requested sort); consider unifying this into a single query or applying limit/sort after combining results to keep paging semantics consistent.
- When `includeSystemIntegrations` is true in `getEndpointsPerCompositeType`, system endpoints are appended after org-specific endpoints, which means they will always appear after all org-specific items regardless of sorting; if system and org endpoints should share the same global ordering, you may want to push the system inclusion into the query itself instead of merging in Java.

## Individual Comments

### Comment 1
<location> `backend/src/main/java/com/redhat/cloud/notifications/db/repositories/EndpointRepository.java:92-95` </location>
<code_context>
     }

-    public List<Endpoint> getEndpointsPerCompositeType(String orgId, @Nullable String name, Set<CompositeEndpointType> type, Boolean activeOnly, Query limiter) {
+    public List<Endpoint> getEndpointsPerCompositeType(String orgId, @Nullable String name, Set<CompositeEndpointType> type, Boolean activeOnly, Query limiter, boolean includeSystemIntegrations) {
         Query.Limit limit = limiter == null ? null : limiter.getLimit();
         Optional<Sort> sort = limiter == null ? Optional.empty() : Sort.getSort(limiter, null, Endpoint.SORT_FIELDS);
</code_context>

<issue_to_address>
**issue (bug_risk):** Combining two separately sorted & limited queries can break global ordering and pagination semantics.

When `includeSystemIntegrations` is `true`, we run two queries with the same `limit` and `sort` and then concatenate them. This means:

* The final list is not globally sorted; each subset is sorted, but their concatenation may violate the requested sort order.
* A `limit` of N can yield up to 2N items, and the resulting page is not equivalent to applying `limit` and `sort` over a single unified dataset.

To preserve the API’s apparent contract of a single, sorted, paginated collection, consider either:

* A single query that includes both org-specific and system endpoints in the `WHERE` clause, or
* Merging both result sets in memory with the requested sort and then applying `limit` once.

If this divergence from global pagination is intentional, it would help to document clearly that this method returns two separately paginated lists concatenated together so callers don’t assume a single consistent page.
</issue_to_address>

### Comment 2
<location> `backend/src/test/java/com/redhat/cloud/notifications/routers/handlers/endpoint/EndpointResourceTest.java:3856-3865` </location>
<code_context>
+    @Test
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding tests for system endpoints combined with filters/pagination and multiple system integrations

The new `testCreateThenReadRegularAndSystemEndpoint` covers V1 vs V2 visibility and `readOnly` for a single regular + system endpoint. To better lock in the new behavior, consider adding tests that:

- List endpoints via V2 with filters (`targetType`, `active`, `name`) to confirm system endpoints are (or aren’t) included as expected.
- Use multiple system endpoints of different types to confirm all appear in V2 with `readOnly = true` while V1 still ignores them.
- (Optional) Handle an org with only system endpoints (no org-bound ones) to confirm V2 still returns them and V1 returns an empty list.

This would strengthen coverage around `includeSystemIntegrations` and `readOnly` handling and help prevent regressions.

Suggested implementation:

```java
    @Test
    void testCreateThenReadRegularAndSystemEndpoint() {

        // Create event type and endpoint
        final Endpoint endpoint = resourceHelpers.createEndpoint(DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, EndpointType.WEBHOOK);
        final Endpoint systemEndpoint = resourceHelpers.createEndpoint(null, null, EndpointType.EMAIL_SUBSCRIPTION);

        final String identityHeaderValue = TestHelpers.encodeRHIdentityInfo(TestConstants.DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, "username");
        final Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue);
        MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS);
    }

    @Test
    void testListEndpointsV2WithFiltersIncludesSystemEndpoints() {
        // Arrange: regular + system endpoints
        final Endpoint regularWebhook = resourceHelpers.createEndpoint(DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, EndpointType.WEBHOOK);
        final Endpoint systemEmail = resourceHelpers.createEndpoint(null, null, EndpointType.EMAIL_SUBSCRIPTION);

        final String identityHeaderValue = TestHelpers.encodeRHIdentityInfo(TestConstants.DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, "username");
        final Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue);
        MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS);

        // Act: list endpoints via V2 with filters that should match both regular + system endpoints
        final io.restassured.path.json.JsonPath json = io.restassured.RestAssured
            .given()
                .header(identityHeader)
                .queryParam("includeSystemIntegrations", true)
                .queryParam("targetType", EndpointType.WEBHOOK.toString())
                .queryParam("active", true)
            .when()
                .get("/api/notifications/v2.0/endpoints") // adjust path if different in this test class
            .then()
                .statusCode(200)
                .extract()
                .jsonPath();

        @SuppressWarnings("unchecked")
        final java.util.List<java.util.Map<String, Object>> data = json.getList("data");

        // Assert: regular endpoint is present and writable
        final boolean regularFound = data.stream().anyMatch(e ->
            DEFAULT_ACCOUNT_ID.equals(e.get("accountId")) &&
                DEFAULT_ORG_ID.equals(e.get("orgId")) &&
                regularWebhook.getId().toString().equals(String.valueOf(e.get("id"))) &&
                Boolean.FALSE.equals(e.get("readOnly"))
        );

        // Assert: system endpoint is present and read-only when includeSystemIntegrations = true
        final boolean systemFoundReadOnly = data.stream().anyMatch(e ->
            e.get("accountId") == null &&
                e.get("orgId") == null &&
                systemEmail.getId().toString().equals(String.valueOf(e.get("id"))) &&
                Boolean.TRUE.equals(e.get("readOnly"))
        );

        org.junit.jupiter.api.Assertions.assertTrue(regularFound, "Regular endpoint should be included in V2 list with filters");
        org.junit.jupiter.api.Assertions.assertTrue(systemFoundReadOnly, "System endpoint should be included in V2 list with filters and marked readOnly");
    }

    @Test
    void testMultipleSystemEndpointsVisibleInV2AndIgnoredInV1() {
        // Arrange: regular endpoint for the org + multiple system endpoints
        final Endpoint regularWebhook = resourceHelpers.createEndpoint(DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, EndpointType.WEBHOOK);
        final Endpoint systemEmail = resourceHelpers.createEndpoint(null, null, EndpointType.EMAIL_SUBSCRIPTION);
        final Endpoint systemWebhook = resourceHelpers.createEndpoint(null, null, EndpointType.WEBHOOK);

        final String identityHeaderValue = TestHelpers.encodeRHIdentityInfo(TestConstants.DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, "username");
        final Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue);
        MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS);

        // Act: list via V1 (should ignore system integrations)
        final io.restassured.path.json.JsonPath v1Json = io.restassured.RestAssured
            .given()
                .header(identityHeader)
            .when()
                .get("/endpoints") // adjust path if different in this test class
            .then()
                .statusCode(200)
                .extract()
                .jsonPath();

        @SuppressWarnings("unchecked")
        final java.util.List<java.util.Map<String, Object>> v1Data = v1Json.getList("data");

        // Assert: V1 only contains the org-bound endpoint(s)
        final boolean v1ContainsRegular = v1Data.stream().anyMatch(e ->
            DEFAULT_ACCOUNT_ID.equals(e.get("accountId")) &&
                DEFAULT_ORG_ID.equals(e.get("orgId")) &&
                regularWebhook.getId().toString().equals(String.valueOf(e.get("id")))
        );
        final boolean v1ContainsSystem = v1Data.stream().anyMatch(e ->
            e.get("accountId") == null && e.get("orgId") == null
        );

        org.junit.jupiter.api.Assertions.assertTrue(v1ContainsRegular, "V1 list should contain the org-bound endpoint");
        org.junit.jupiter.api.Assertions.assertFalse(v1ContainsSystem, "V1 list should not contain system endpoints");

        // Act: list via V2 with includeSystemIntegrations = true
        final io.restassured.path.json.JsonPath v2Json = io.restassured.RestAssured
            .given()
                .header(identityHeader)
                .queryParam("includeSystemIntegrations", true)
            .when()
                .get("/api/notifications/v2.0/endpoints") // adjust path if different in this test class
            .then()
                .statusCode(200)
                .extract()
                .jsonPath();

        @SuppressWarnings("unchecked")
        final java.util.List<java.util.Map<String, Object>> v2Data = v2Json.getList("data");

        // Assert: V2 contains regular + both system endpoints, with all system endpoints readOnly
        final long regularCount = v2Data.stream().filter(e ->
            DEFAULT_ACCOUNT_ID.equals(e.get("accountId")) &&
                DEFAULT_ORG_ID.equals(e.get("orgId")) &&
                Boolean.FALSE.equals(e.get("readOnly"))
        ).count();

        final long systemCount = v2Data.stream().filter(e ->
            e.get("accountId") == null &&
                e.get("orgId") == null &&
                Boolean.TRUE.equals(e.get("readOnly"))
        ).count();

        org.junit.jupiter.api.Assertions.assertEquals(1, regularCount, "V2 list should contain exactly one regular endpoint for the org");
        org.junit.jupiter.api.Assertions.assertTrue(systemCount >= 2, "V2 list should contain both system endpoints and mark them as readOnly");
    }

    @Test
    void testOrgWithOnlySystemEndpointsV2ReturnsThemWhileV1IsEmpty() {
        // Arrange: only system endpoints exist for this test
        final Endpoint systemEmail = resourceHelpers.createEndpoint(null, null, EndpointType.EMAIL_SUBSCRIPTION);
        final Endpoint systemWebhook = resourceHelpers.createEndpoint(null, null, EndpointType.WEBHOOK);

        final String identityHeaderValue = TestHelpers.encodeRHIdentityInfo(TestConstants.DEFAULT_ACCOUNT_ID, DEFAULT_ORG_ID, "username");
        final Header identityHeader = TestHelpers.createRHIdentityHeader(identityHeaderValue);
        MockServerConfig.addMockRbacAccess(identityHeaderValue, FULL_ACCESS);

        // Act: V1 list should be empty for this org/account
        final io.restassured.path.json.JsonPath v1Json = io.restassured.RestAssured
            .given()
                .header(identityHeader)
            .when()
                .get("/endpoints") // adjust path if different in this test class
            .then()
                .statusCode(200)
                .extract()
                .jsonPath();

        @SuppressWarnings("unchecked")
        final java.util.List<java.util.Map<String, Object>> v1Data = v1Json.getList("data");
        org.junit.jupiter.api.Assertions.assertTrue(v1Data == null || v1Data.isEmpty(),
            "V1 list should be empty when only system endpoints exist");

        // Act: V2 list with includeSystemIntegrations = true should still return system endpoints
        final io.restassured.path.json.JsonPath v2Json = io.restassured.RestAssured
            .given()
                .header(identityHeader)
                .queryParam("includeSystemIntegrations", true)
            .when()
                .get("/api/notifications/v2.0/endpoints") // adjust path if different in this test class
            .then()
                .statusCode(200)
                .extract()
                .jsonPath();

        @SuppressWarnings("unchecked")
        final java.util.List<java.util.Map<String, Object>> v2Data = v2Json.getList("data");

        final boolean emailSystemFound = v2Data.stream().anyMatch(e ->
            e.get("accountId") == null &&
                e.get("orgId") == null &&
                systemEmail.getId().toString().equals(String.valueOf(e.get("id"))) &&
                Boolean.TRUE.equals(e.get("readOnly"))
        );

        final boolean webhookSystemFound = v2Data.stream().anyMatch(e ->
            e.get("accountId") == null &&
                e.get("orgId") == null &&
                systemWebhook.getId().toString().equals(String.valueOf(e.get("id"))) &&
                Boolean.TRUE.equals(e.get("readOnly"))
        );

        org.junit.jupiter.api.Assertions.assertTrue(emailSystemFound, "V2 list should include the system email endpoint and mark it as readOnly");
        org.junit.jupiter.api.Assertions.assertTrue(webhookSystemFound, "V2 list should include the system webhook endpoint and mark it as readOnly");
    }

```

1. Adjust the V1 and V2 endpoint URLs used in the tests (`/endpoints` and `/api/notifications/v2.0/endpoints`) to match the existing conventions in `EndpointResourceTest` (e.g. they might be `/api/notifications/v1.0/endpoints`, `/api/notifications/v2.0/endpoints`, or include a base path from a constant).
2. If the JSON structure returned by the list endpoints does not use `data` as the array field, or if field names differ (`accountId`, `orgId`, `id`, `readOnly`), update the JSONPath expressions and map key lookups accordingly.
3. If there’s already a helper to list endpoints (e.g. `listEndpointsV1`, `listEndpointsV2`, or a common `getEndpoints(...)` method), replace the direct RestAssured calls with those helpers to keep the tests consistent with the rest of the class.
4. Ensure `RestAssured` and `JsonPath` static imports are consistent with the rest of the file (you may prefer static imports like `given()`, `when()`, `then()` and `assertThat` instead of fully qualified names).
5. If the test class uses a different identity/mock RBAC setup pattern (e.g. helper methods to build `identityHeader` and grant permissions), reuse those helpers in these tests instead of duplicating logic.
6. Verify that the test data setup (especially `resourceHelpers.createEndpoint(null, null, ...)`) is the same mechanism used elsewhere in the test suite to create system endpoints; if system endpoints require specific flags or types, adjust the `EndpointType` and creation logic accordingly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@g-duval g-duval changed the title [RHCLOUD-44561] Add system endpoints on Endpointd Api V2 [RHCLOUD-44561] Add system endpoints on Endpoint Api V2 Feb 2, 2026
@gwenneg gwenneg self-assigned this Mar 20, 2026
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