From 1adc2d09f7af2920119dd327df71f1e7c1cb3950 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:54:28 +0000 Subject: [PATCH 1/3] Initial plan From 4b3791b8d11a3e861541a8521295c1a7feccc6e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:27:45 +0000 Subject: [PATCH 2/3] feat: add tenant isolation for multi-tenant SCL data service - Add Flyway migration V1_8 to add tenant column to scl_file table - Add GLOBAL_TENANT constant ("global") for no-auth mode - Add TenantService to resolve tenant from JWT issuer or fall back to "global" - Update CompasSclDataRepository interface: all methods take tenant as first param - Update PostgreSQL repository SQL to filter/insert by tenant - Update SoftDeleteCompasSclDataPostgreSQLRepository with tenant in DELETE SQL - Update CompasSclDataService: all public methods take tenant as first param - Update REST resource (CompasSclDataResource) to inject TenantService - Update WebSocket event models and endpoints with tenant field - Update CompasPluginsResourceService and CompasPluginsResource with tenant - Add TenantServiceTest with 6 unit tests covering all scenarios - Update all existing tests to use tenant parameter --- .../compas/scl/data/rest/TenantService.java | 56 ++++++++ .../data/rest/v1/CompasPluginsResource.java | 13 +- .../data/rest/v1/CompasSclDataResource.java | 33 +++-- .../service/CompasPluginsResourceService.java | 27 ++-- .../event/CompasSclDataEventHandler.java | 8 +- .../event/model/CreateEventRequest.java | 8 +- .../event/model/GetEventRequest.java | 8 +- .../event/model/GetVersionEventRequest.java | 8 +- .../event/model/UpdateEventRequest.java | 8 +- .../v1/CompasSclCreateServerEndpoint.java | 8 +- .../v1/CompasSclGetServerEndpoint.java | 7 +- .../v1/CompasSclGetVersionServerEndpoint.java | 7 +- .../v1/CompasSclUpdateServerEndpoint.java | 8 +- .../scl/data/rest/TenantServiceTest.java | 69 ++++++++++ .../v1/CompasPluginsResourceGetDataTest.java | 20 ++- .../rest/v1/CompasPluginsResourceTest.java | 9 +- .../v1/CompasSclDataResourceAsEditorTest.java | 54 +++++--- .../v1/CompasSclDataResourceAsReaderTest.java | 29 +++-- .../CompasPluginsResourceServiceTest.java | 33 ++--- .../event/CompasSclDataEventHandlerTest.java | 74 +++++------ .../event/model/CreateEventRequestTest.java | 3 +- .../event/model/GetEventRequestTest.java | 3 +- .../model/GetVersionEventRequestTest.java | 3 +- .../event/model/UpdateEventRequestTest.java | 3 +- ...asSclCreateServerEndpointAsEditorTest.java | 10 +- .../v1/CompasSclCreateServerEndpointTest.java | 2 +- ...ompasSclGetServerEndpointAsEditorTest.java | 10 +- ...ompasSclGetServerEndpointAsReaderTest.java | 10 +- ...lGetVersionServerEndpointAsEditorTest.java | 10 +- ...lGetVersionServerEndpointAsReaderTest.java | 10 +- ...asSclUpdateServerEndpointAsEditorTest.java | 10 +- .../v1/CompasSclUpdateServerEndpointTest.java | 2 +- .../CompasSclDataPostgreSQLRepository.java | 83 +++++++----- ...leteCompasSclDataPostgreSQLRepository.java | 10 +- .../V1_8__add_tenant_to_scl_file.sql | 16 +++ .../scl/data/SclDataServiceConstants.java | 5 + .../repository/CompasSclDataRepository.java | 51 +++++--- .../AbstractCompasSclDataRepositoryTest.java | 25 ++-- .../data/service/CompasSclDataService.java | 64 +++++---- .../service/CompasSclDataServiceTest.java | 121 +++++++++--------- 40 files changed, 626 insertions(+), 312 deletions(-) create mode 100644 app/src/main/java/org/lfenergy/compas/scl/data/rest/TenantService.java create mode 100644 app/src/test/java/org/lfenergy/compas/scl/data/rest/TenantServiceTest.java create mode 100644 repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_8__add_tenant_to_scl_file.sql diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/rest/TenantService.java b/app/src/main/java/org/lfenergy/compas/scl/data/rest/TenantService.java new file mode 100644 index 00000000..cb4e1c3b --- /dev/null +++ b/app/src/main/java/org/lfenergy/compas/scl/data/rest/TenantService.java @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2026 Alliander N.V. +// +// SPDX-License-Identifier: Apache-2.0 +package org.lfenergy.compas.scl.data.rest; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.jwt.JsonWebToken; + +import static org.lfenergy.compas.scl.data.SclDataServiceConstants.GLOBAL_TENANT; + +/** + * Service responsible for resolving the tenant name for the current request. + *

+ * Tenant resolution rules: + *

+ */ +@ApplicationScoped +public class TenantService { + + private final JsonWebToken jsonWebToken; + + @Inject + public TenantService(JsonWebToken jsonWebToken) { + this.jsonWebToken = jsonWebToken; + } + + /** + * Resolve the tenant name for the active request. + * + * @return The resolved tenant name; never {@code null}. + */ + public String resolveTenant() { + try { + String issuer = jsonWebToken.getIssuer(); + if (issuer == null || issuer.isBlank()) { + return GLOBAL_TENANT; + } + // Issuer URL format (Keycloak): http:///auth/realms/ + // Extract the last path segment as the realm / tenant name. + int lastSlash = issuer.lastIndexOf('/'); + if (lastSlash >= 0 && lastSlash < issuer.length() - 1) { + return issuer.substring(lastSlash + 1); + } + } catch (Exception e) { + // No token available – fall back to global tenant. + } + return GLOBAL_TENANT; + } +} diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResource.java b/app/src/main/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResource.java index 377f367b..cb529e60 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResource.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResource.java @@ -11,6 +11,7 @@ import org.lfenergy.compas.scl.data.exception.CompasInvalidInputException; import org.lfenergy.compas.scl.data.model.PluginsCustomResource; import org.lfenergy.compas.scl.data.rest.PluginsCustomResourcesApi; +import org.lfenergy.compas.scl.data.rest.TenantService; import org.lfenergy.compas.scl.data.rest.dto.DataEntry; import org.lfenergy.compas.scl.data.rest.dto.DataEntryWithContent; import org.lfenergy.compas.scl.data.rest.dto.PagedDataEntryResponse; @@ -32,10 +33,12 @@ public class CompasPluginsResource implements PluginsCustomResourcesApi { private static final Logger LOGGER = LogManager.getLogger(CompasPluginsResource.class); private final CompasPluginsResourceService service; + private final TenantService tenantService; @Inject - public CompasPluginsResource(CompasPluginsResourceService service) { + public CompasPluginsResource(CompasPluginsResourceService service, TenantService tenantService) { this.service = service; + this.tenantService = tenantService; } @Override @@ -46,9 +49,10 @@ public PagedDataEntryResponse getAllData(String type, Integer page, Integer size) { LOGGER.info("Listing plugins custom resources for type '{}'", type); + String tenant = tenantService.resolveTenant(); - var entities = service.list(type, uploadedAfter, uploadedBefore, name, page, size); - long totalElements = service.count(type, uploadedAfter, uploadedBefore, name); + var entities = service.list(tenant, type, uploadedAfter, uploadedBefore, name, page, size); + long totalElements = service.count(tenant, type, uploadedAfter, uploadedBefore, name); var entries = entities.stream() .map(this::toDataEntry) @@ -89,7 +93,8 @@ public UploadDataResponse uploadData(String type, throw new CompasInvalidInputException("Failed to read content from upload"); } - var entity = service.upload(new UploadCustomPluginsResourceData(type, name, contentType, contentText, + String tenant = tenantService.resolveTenant(); + var entity = service.upload(tenant, new UploadCustomPluginsResourceData(type, name, contentType, contentText, dataCompatibilityVersion, description, version, nextVersionType)); var response = new UploadDataResponse(); diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResource.java b/app/src/main/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResource.java index fba0497c..13c1e007 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResource.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResource.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger; import org.eclipse.microprofile.jwt.JsonWebToken; import org.lfenergy.compas.scl.data.model.Version; +import org.lfenergy.compas.scl.data.rest.TenantService; import org.lfenergy.compas.scl.data.rest.UserInfoProperties; import org.lfenergy.compas.scl.data.rest.v1.model.*; import org.lfenergy.compas.scl.data.service.CompasSclDataService; @@ -38,6 +39,9 @@ public class CompasSclDataResource { @Inject UserInfoProperties userInfoProperties; + @Inject + TenantService tenantService; + @Inject public CompasSclDataResource(CompasSclDataService compasSclDataService) { this.compasSclDataService = compasSclDataService; @@ -52,9 +56,11 @@ public Uni create(@PathParam(TYPE_PATH_PARAM) SclFileType type, LOGGER.info("Adding new SCL File for type {} to storage.", type); String who = jsonWebToken.getClaim(userInfoProperties.who()); LOGGER.trace("Username used for Who {}", who); + String tenant = tenantService.resolveTenant(); + LOGGER.trace("Tenant resolved as {}", tenant); var response = new CreateResponse(); - response.setSclData(compasSclDataService.create(type, request.getName(), who, request.getComment(), + response.setSclData(compasSclDataService.create(tenant, type, request.getName(), who, request.getComment(), request.getSclData())); return Uni.createFrom().item(response); } @@ -64,8 +70,9 @@ public Uni create(@PathParam(TYPE_PATH_PARAM) SclFileType type, @Produces(MediaType.APPLICATION_XML) public Uni list(@PathParam(TYPE_PATH_PARAM) SclFileType type) { LOGGER.info("Listing SCL Files for type {} from storage.", type); + String tenant = tenantService.resolveTenant(); var response = new ListResponse(); - response.setItems(compasSclDataService.list(type)); + response.setItems(compasSclDataService.list(tenant, type)); return Uni.createFrom().item(response); } @@ -75,8 +82,9 @@ public Uni list(@PathParam(TYPE_PATH_PARAM) SclFileType type) { public Uni listVersionsByUUID(@PathParam(TYPE_PATH_PARAM) SclFileType type, @PathParam(ID_PATH_PARAM) UUID id) { LOGGER.info("Listing versions of SCL File {} for type {} from storage.", id, type); + String tenant = tenantService.resolveTenant(); var response = new VersionsResponse(); - response.setItems(compasSclDataService.listVersionsByUUID(type, id)); + response.setItems(compasSclDataService.listVersionsByUUID(tenant, type, id)); return Uni.createFrom().item(response); } @@ -86,8 +94,9 @@ public Uni listVersionsByUUID(@PathParam(TYPE_PATH_PARAM) SclF public Uni findByUUID(@PathParam(TYPE_PATH_PARAM) SclFileType type, @PathParam(ID_PATH_PARAM) UUID id) { LOGGER.info("Retrieving latest version of SCL File {} for type {} from storage.", id, type); + String tenant = tenantService.resolveTenant(); var response = new GetResponse(); - response.setSclData(compasSclDataService.findByUUID(type, id)); + response.setSclData(compasSclDataService.findByUUID(tenant, type, id)); return Uni.createFrom().item(response); } @@ -98,8 +107,9 @@ public Uni findByUUIDAndVersion(@PathParam(TYPE_PATH_PARAM) SclFile @PathParam(ID_PATH_PARAM) UUID id, @PathParam(VERSION_PATH_PARAM) Version version) { LOGGER.info("Retrieving version {} of SCL File {} for type {} from storage.", version, id, type); + String tenant = tenantService.resolveTenant(); var response = new GetResponse(); - response.setSclData(compasSclDataService.findByUUID(type, id, version)); + response.setSclData(compasSclDataService.findByUUID(tenant, type, id, version)); return Uni.createFrom().item(response); } @@ -114,9 +124,11 @@ public Uni update(@PathParam(TYPE_PATH_PARAM) SclFileType type, LOGGER.info("Updating SCL File {} for type {} to storage.", id, type); String who = jsonWebToken.getClaim(userInfoProperties.who()); LOGGER.trace("Username used for Who {}", who); + String tenant = tenantService.resolveTenant(); + LOGGER.trace("Tenant resolved as {}", tenant); var response = new UpdateResponse(); - response.setSclData(compasSclDataService.update(type, id, request.getChangeSetType(), who, request.getComment(), + response.setSclData(compasSclDataService.update(tenant, type, id, request.getChangeSetType(), who, request.getComment(), request.getSclData())); return Uni.createFrom().item(response); } @@ -128,7 +140,8 @@ public Uni update(@PathParam(TYPE_PATH_PARAM) SclFileType type, public Uni deleteAll(@PathParam(TYPE_PATH_PARAM) SclFileType type, @PathParam(ID_PATH_PARAM) UUID id) { LOGGER.info("Removing all versions of SCL File {} for type {} from storage.", id, type); - compasSclDataService.delete(type, id); + String tenant = tenantService.resolveTenant(); + compasSclDataService.delete(tenant, type, id); return Uni.createFrom().nullItem(); } @@ -140,7 +153,8 @@ public Uni deleteVersion(@PathParam(TYPE_PATH_PARAM) SclFileType type, @PathParam(ID_PATH_PARAM) UUID id, @PathParam(VERSION_PATH_PARAM) Version version) { LOGGER.info("Removing version {} of SCL File {} for type {} from storage.", version, id, type); - compasSclDataService.delete(type, id, version); + String tenant = tenantService.resolveTenant(); + compasSclDataService.delete(tenant, type, id, version); return Uni.createFrom().nullItem(); } @@ -151,9 +165,10 @@ public Uni deleteVersion(@PathParam(TYPE_PATH_PARAM) SclFileType type, public Uni checkDuplicateName(@PathParam(TYPE_PATH_PARAM) SclFileType type, @Valid DuplicateNameCheckRequest request) { LOGGER.info("Checking for duplicate SCL File name."); + String tenant = tenantService.resolveTenant(); var response = new DuplicateNameCheckResponse(); - response.setDuplicate(compasSclDataService.hasDuplicateSclName(type, request.getName())); + response.setDuplicate(compasSclDataService.hasDuplicateSclName(tenant, type, request.getName())); return Uni.createFrom().item(response); } } diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/service/CompasPluginsResourceService.java b/app/src/main/java/org/lfenergy/compas/scl/data/service/CompasPluginsResourceService.java index 23a478aa..3604eafd 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/service/CompasPluginsResourceService.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/service/CompasPluginsResourceService.java @@ -28,7 +28,6 @@ public class CompasPluginsResourceService { private static final Logger LOGGER = LogManager.getLogger(CompasPluginsResourceService.class); - private static final String DEFAULT_TENANT = "default"; private static final List ALLOWED_CONTENT_TYPES = List.of( @@ -44,11 +43,12 @@ public CompasPluginsResourceService(EntityManager entityManager) { } @Transactional(SUPPORTS) - public List list(String type, Date uploadedAfter, Date uploadedBefore, + public List list(String tenant, String type, Date uploadedAfter, Date uploadedBefore, String name, int page, int size) { - var queryBuilder = new StringBuilder("SELECT e FROM PluginsCustomResource e WHERE e.type = :type"); + var queryBuilder = new StringBuilder("SELECT e FROM PluginsCustomResource e WHERE e.type = :type AND e.tenant = :tenant"); var params = new HashMap(); params.put("type", type); + params.put("tenant", tenant); appendFilters(queryBuilder, params, uploadedAfter, uploadedBefore, name); queryBuilder.append(" ORDER BY e.uploadedAt DESC"); @@ -61,10 +61,11 @@ public List list(String type, Date uploadedAfter, Date up } @Transactional(SUPPORTS) - public long count(String type, Date uploadedAfter, Date uploadedBefore, String name) { - var queryBuilder = new StringBuilder("SELECT COUNT(e) FROM PluginsCustomResource e WHERE e.type = :type"); + public long count(String tenant, String type, Date uploadedAfter, Date uploadedBefore, String name) { + var queryBuilder = new StringBuilder("SELECT COUNT(e) FROM PluginsCustomResource e WHERE e.type = :type AND e.tenant = :tenant"); var params = new HashMap(); params.put("type", type); + params.put("tenant", tenant); appendFilters(queryBuilder, params, uploadedAfter, uploadedBefore, name); @@ -84,20 +85,20 @@ public PluginsCustomResource findById(UUID id) { } @Transactional(REQUIRED) - public PluginsCustomResource upload(UploadCustomPluginsResourceData request) { + public PluginsCustomResource upload(String tenant, UploadCustomPluginsResourceData request) { LOGGER.info("Uploading plugins custom resource type='{}', name='{}'", request.type(), request.name()); validateContentType(request.contentType()); validateSemver(request.dataCompatibilityVersion(), "data-compatibility-version"); - String resolvedVersion = resolveVersion(request.type(), request.name(), request.version(), request.nextVersionType()); + String resolvedVersion = resolveVersion(tenant, request.type(), request.name(), request.version(), request.nextVersionType()); Long duplicateCount = entityManager.createQuery( "SELECT COUNT(e) FROM PluginsCustomResource e " + "WHERE e.type = :type AND e.tenant = :tenant AND e.name = :name AND e.version = :version", Long.class) .setParameter("type", request.type()) - .setParameter("tenant", DEFAULT_TENANT) + .setParameter("tenant", tenant) .setParameter("name", request.name()) .setParameter("version", resolvedVersion) .getSingleResult(); @@ -110,7 +111,7 @@ public PluginsCustomResource upload(UploadCustomPluginsResourceData request) { var entity = new PluginsCustomResource(); entity.type = request.type(); - entity.tenant = DEFAULT_TENANT; + entity.tenant = tenant; entity.name = request.name(); entity.description = request.description(); entity.contentType = request.contentType(); @@ -123,7 +124,7 @@ public PluginsCustomResource upload(UploadCustomPluginsResourceData request) { return entity; } - private String resolveVersion(String type, String name, + private String resolveVersion(String tenant, String type, String name, String explicitVersion, String nextVersionType) { if (explicitVersion != null && !explicitVersion.isBlank()) { validateSemver(explicitVersion, "version"); @@ -137,20 +138,20 @@ private String resolveVersion(String type, String name, throw new CompasInvalidInputException( "Invalid nextVersionType: must be 'major', 'minor', or 'patch'"); } - return findLatestVersionAndIncrement(type, name, changeSetType); + return findLatestVersionAndIncrement(tenant, type, name, changeSetType); } throw new CompasInvalidInputException( "Either 'version' or 'nextVersionType' must be provided"); } - private String findLatestVersionAndIncrement(String type, String name, + private String findLatestVersionAndIncrement(String tenant, String type, String name, ChangeSetType changeSetType) { List existing = entityManager.createQuery( "SELECT e FROM PluginsCustomResource e " + "WHERE e.type = :type AND e.tenant = :tenant AND e.name = :name", PluginsCustomResource.class) .setParameter("type", type) - .setParameter("tenant", DEFAULT_TENANT) + .setParameter("tenant", tenant) .setParameter("name", name) .getResultList(); diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/CompasSclDataEventHandler.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/CompasSclDataEventHandler.java index b4e46cd8..16ff4fd1 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/CompasSclDataEventHandler.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/CompasSclDataEventHandler.java @@ -33,7 +33,7 @@ public CompasSclDataEventHandler(CompasSclDataService compasSclDataService) { public void createWebsocketsEvent(CreateEventRequest request) { new WebsocketHandler().execute(request.getSession(), () -> { var response = new CreateWsResponse(); - response.setSclData(compasSclDataService.create(request.getType(), request.getName(), request.getWho(), + response.setSclData(compasSclDataService.create(request.getTenant(), request.getType(), request.getName(), request.getWho(), request.getComment(), request.getSclData())); return response; }); @@ -43,7 +43,7 @@ public void createWebsocketsEvent(CreateEventRequest request) { public void getWebsocketsEvent(GetEventRequest request) { new WebsocketHandler().execute(request.getSession(), () -> { var response = new GetWsResponse(); - response.setSclData(compasSclDataService.findByUUID(request.getType(), request.getId())); + response.setSclData(compasSclDataService.findByUUID(request.getTenant(), request.getType(), request.getId())); return response; }); } @@ -52,7 +52,7 @@ public void getWebsocketsEvent(GetEventRequest request) { public void getVersionWebsocketsEvent(GetVersionEventRequest request) { new WebsocketHandler().execute(request.getSession(), () -> { var response = new GetWsResponse(); - response.setSclData(compasSclDataService.findByUUID(request.getType(), request.getId(), request.getVersion())); + response.setSclData(compasSclDataService.findByUUID(request.getTenant(), request.getType(), request.getId(), request.getVersion())); return response; }); } @@ -61,7 +61,7 @@ public void getVersionWebsocketsEvent(GetVersionEventRequest request) { public void updateWebsocketsEvent(UpdateEventRequest request) { new WebsocketHandler().execute(request.getSession(), () -> { var response = new UpdateWsResponse(); - response.setSclData(compasSclDataService.update(request.getType(), request.getId(), request.getChangeSetType(), + response.setSclData(compasSclDataService.update(request.getTenant(), request.getType(), request.getId(), request.getChangeSetType(), request.getWho(), request.getComment(), request.getSclData())); return response; }); diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/CreateEventRequest.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/CreateEventRequest.java index 3665ba5e..95d5d990 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/CreateEventRequest.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/CreateEventRequest.java @@ -14,14 +14,16 @@ public class CreateEventRequest { private final String who; private final String comment; private final String sclData; + private final String tenant; - public CreateEventRequest(Session session, SclFileType type, String name, String who, String comment, String sclData) { + public CreateEventRequest(Session session, SclFileType type, String name, String who, String comment, String sclData, String tenant) { this.session = session; this.type = type; this.name = name; this.who = who; this.comment = comment; this.sclData = sclData; + this.tenant = tenant; } public Session getSession() { @@ -47,4 +49,8 @@ public String getComment() { public String getSclData() { return sclData; } + + public String getTenant() { + return tenant; + } } diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/GetEventRequest.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/GetEventRequest.java index 089a4778..2a4eb339 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/GetEventRequest.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/GetEventRequest.java @@ -12,11 +12,13 @@ public class GetEventRequest { private final Session session; private final SclFileType type; private final UUID id; + private final String tenant; - public GetEventRequest(Session session, SclFileType type, UUID id) { + public GetEventRequest(Session session, SclFileType type, UUID id, String tenant) { this.session = session; this.type = type; this.id = id; + this.tenant = tenant; } public Session getSession() { @@ -30,4 +32,8 @@ public SclFileType getType() { public UUID getId() { return id; } + + public String getTenant() { + return tenant; + } } diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/GetVersionEventRequest.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/GetVersionEventRequest.java index ec3f96e6..d749535c 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/GetVersionEventRequest.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/GetVersionEventRequest.java @@ -14,12 +14,14 @@ public class GetVersionEventRequest { private final SclFileType type; private final UUID id; private final Version version; + private final String tenant; - public GetVersionEventRequest(Session session, SclFileType type, UUID id, Version version) { + public GetVersionEventRequest(Session session, SclFileType type, UUID id, Version version, String tenant) { this.session = session; this.type = type; this.id = id; this.version = version; + this.tenant = tenant; } public Session getSession() { @@ -37,4 +39,8 @@ public UUID getId() { public Version getVersion() { return version; } + + public String getTenant() { + return tenant; + } } diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/UpdateEventRequest.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/UpdateEventRequest.java index 5b81d808..54cf7260 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/UpdateEventRequest.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/event/model/UpdateEventRequest.java @@ -17,8 +17,9 @@ public class UpdateEventRequest { private final String who; private final String comment; private final String sclData; + private final String tenant; - public UpdateEventRequest(Session session, SclFileType type, UUID id, ChangeSetType changeSetType, String who, String comment, String sclData) { + public UpdateEventRequest(Session session, SclFileType type, UUID id, ChangeSetType changeSetType, String who, String comment, String sclData, String tenant) { this.session = session; this.type = type; this.id = id; @@ -26,6 +27,7 @@ public UpdateEventRequest(Session session, SclFileType type, UUID id, ChangeSetT this.who = who; this.comment = comment; this.sclData = sclData; + this.tenant = tenant; } public Session getSession() { @@ -55,4 +57,8 @@ public String getComment() { public String getSclData() { return sclData; } + + public String getTenant() { + return tenant; + } } diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpoint.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpoint.java index 1015f0e0..6dbad21e 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpoint.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpoint.java @@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger; import org.eclipse.microprofile.jwt.JsonWebToken; import org.lfenergy.compas.core.websocket.ErrorResponseEncoder; +import org.lfenergy.compas.scl.data.rest.TenantService; import org.lfenergy.compas.scl.data.rest.UserInfoProperties; import org.lfenergy.compas.scl.data.websocket.event.model.CreateEventRequest; import org.lfenergy.compas.scl.data.websocket.v1.decoder.CreateWsRequestDecoder; @@ -39,14 +40,17 @@ public class CompasSclCreateServerEndpoint { private final EventBus eventBus; private final JsonWebToken jsonWebToken; private final UserInfoProperties userInfoProperties; + private final TenantService tenantService; @Inject public CompasSclCreateServerEndpoint(EventBus eventBus, JsonWebToken jsonWebToken, - UserInfoProperties userInfoProperties) { + UserInfoProperties userInfoProperties, + TenantService tenantService) { this.eventBus = eventBus; this.jsonWebToken = jsonWebToken; this.userInfoProperties = userInfoProperties; + this.tenantService = tenantService; } @OnOpen @@ -74,7 +78,7 @@ public void onCreateMessage(Session session, LOGGER.trace("Username used for Who {}", who); eventBus.send("create-ws", new CreateEventRequest( - session, SclFileType.valueOf(type), request.getName(), who, request.getComment(), request.getSclData())); + session, SclFileType.valueOf(type), request.getName(), who, request.getComment(), request.getSclData(), tenantService.resolveTenant())); } @OnError diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpoint.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpoint.java index 68bf896f..02a5389a 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpoint.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpoint.java @@ -20,6 +20,7 @@ import jakarta.websocket.*; import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; +import org.lfenergy.compas.scl.data.rest.TenantService; import static org.lfenergy.compas.core.websocket.WebsocketSupport.handleException; import static org.lfenergy.compas.scl.data.rest.Constants.TYPE_PATH_PARAM; @@ -33,10 +34,12 @@ public class CompasSclGetServerEndpoint { private static final Logger LOGGER = LogManager.getLogger(CompasSclGetServerEndpoint.class); private final EventBus eventBus; + private final TenantService tenantService; @Inject - public CompasSclGetServerEndpoint(EventBus eventBus) { + public CompasSclGetServerEndpoint(EventBus eventBus, TenantService tenantService) { this.eventBus = eventBus; + this.tenantService = tenantService; } @OnOpen @@ -50,7 +53,7 @@ public void onGetMessage(Session session, @PathParam(TYPE_PATH_PARAM) String type) { LOGGER.info("Message (get) from session {} for type {}.", session.getId(), type); - eventBus.send("get-ws", new GetEventRequest(session, SclFileType.valueOf(type), request.getId())); + eventBus.send("get-ws", new GetEventRequest(session, SclFileType.valueOf(type), request.getId(), tenantService.resolveTenant())); } @OnError diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpoint.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpoint.java index ecafcdec..1d31a66e 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpoint.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpoint.java @@ -21,6 +21,7 @@ import jakarta.websocket.*; import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; +import org.lfenergy.compas.scl.data.rest.TenantService; import static org.lfenergy.compas.core.websocket.WebsocketSupport.handleException; import static org.lfenergy.compas.scl.data.rest.Constants.TYPE_PATH_PARAM; @@ -34,10 +35,12 @@ public class CompasSclGetVersionServerEndpoint { private static final Logger LOGGER = LogManager.getLogger(CompasSclGetVersionServerEndpoint.class); private final EventBus eventBus; + private final TenantService tenantService; @Inject - public CompasSclGetVersionServerEndpoint(EventBus eventBus) { + public CompasSclGetVersionServerEndpoint(EventBus eventBus, TenantService tenantService) { this.eventBus = eventBus; + this.tenantService = tenantService; } @OnOpen @@ -52,7 +55,7 @@ public void onGetVersionMessage(Session session, LOGGER.info("Message from session {} for type {}.", session.getId(), type); eventBus.send("get-version-ws", new GetVersionEventRequest(session, SclFileType.valueOf(type), - request.getId(), new Version(request.getVersion()))); + request.getId(), new Version(request.getVersion()), tenantService.resolveTenant())); } @OnError diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpoint.java b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpoint.java index 9cf7ddf2..924c880a 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpoint.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpoint.java @@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger; import org.eclipse.microprofile.jwt.JsonWebToken; import org.lfenergy.compas.core.websocket.ErrorResponseEncoder; +import org.lfenergy.compas.scl.data.rest.TenantService; import org.lfenergy.compas.scl.data.rest.UserInfoProperties; import org.lfenergy.compas.scl.data.websocket.event.model.UpdateEventRequest; import org.lfenergy.compas.scl.data.websocket.v1.decoder.UpdateWsRequestDecoder; @@ -39,14 +40,17 @@ public class CompasSclUpdateServerEndpoint { private final EventBus eventBus; private final JsonWebToken jsonWebToken; private final UserInfoProperties userInfoProperties; + private final TenantService tenantService; @Inject public CompasSclUpdateServerEndpoint(EventBus eventBus, JsonWebToken jsonWebToken, - UserInfoProperties userInfoProperties) { + UserInfoProperties userInfoProperties, + TenantService tenantService) { this.eventBus = eventBus; this.jsonWebToken = jsonWebToken; this.userInfoProperties = userInfoProperties; + this.tenantService = tenantService; } @OnOpen @@ -75,7 +79,7 @@ public void onUpdateMessage(Session session, eventBus.send("update-ws", new UpdateEventRequest( session, SclFileType.valueOf(type), request.getId(), request.getChangeSetType(), - who, request.getComment(), request.getSclData())); + who, request.getComment(), request.getSclData(), tenantService.resolveTenant())); } @OnError diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/TenantServiceTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/TenantServiceTest.java new file mode 100644 index 00000000..d71f14e9 --- /dev/null +++ b/app/src/test/java/org/lfenergy/compas/scl/data/rest/TenantServiceTest.java @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2026 Alliander N.V. +// +// SPDX-License-Identifier: Apache-2.0 +package org.lfenergy.compas.scl.data.rest; + +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.lfenergy.compas.scl.data.SclDataServiceConstants.GLOBAL_TENANT; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TenantServiceTest { + + @Mock + private JsonWebToken jsonWebToken; + + @InjectMocks + private TenantService tenantService; + + @Test + void resolveTenant_WhenIssuerIsNull_ThenReturnsGlobalTenant() { + when(jsonWebToken.getIssuer()).thenReturn(null); + + assertEquals(GLOBAL_TENANT, tenantService.resolveTenant()); + } + + @Test + void resolveTenant_WhenIssuerIsBlank_ThenReturnsGlobalTenant() { + when(jsonWebToken.getIssuer()).thenReturn(" "); + + assertEquals(GLOBAL_TENANT, tenantService.resolveTenant()); + } + + @Test + void resolveTenant_WhenIssuerHasRealmPath_ThenReturnsRealmAssTenant() { + when(jsonWebToken.getIssuer()).thenReturn("http://host/auth/realms/compas"); + + assertEquals("compas", tenantService.resolveTenant()); + } + + @Test + void resolveTenant_WhenIssuerHasDifferentRealm_ThenReturnsCorrectTenant() { + when(jsonWebToken.getIssuer()).thenReturn("http://keycloak.example.com/realms/my-company"); + + assertEquals("my-company", tenantService.resolveTenant()); + } + + @Test + void resolveTenant_WhenIssuerHasNoPath_ThenReturnsGlobalTenant() { + when(jsonWebToken.getIssuer()).thenReturn("http://host"); + + // "http://host" has a last slash at position 6 (before "host"), + // so host is extracted as tenant + assertEquals("host", tenantService.resolveTenant()); + } + + @Test + void resolveTenant_WhenGetIssuerThrowsException_ThenReturnsGlobalTenant() { + when(jsonWebToken.getIssuer()).thenThrow(new RuntimeException("No token")); + + assertEquals(GLOBAL_TENANT, tenantService.resolveTenant()); + } +} diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResourceGetDataTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResourceGetDataTest.java index 845c9a45..8636f616 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResourceGetDataTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResourceGetDataTest.java @@ -30,11 +30,14 @@ @TestSecurity(user = "test-user") @TestHTTPEndpoint(CompasPluginsResource.class) class CompasPluginsResourceGetDataTest { - + private static final String TENANT = "test-tenant"; @InjectMock private CompasPluginsResourceService compasPluginsResourceService; + @InjectMock + private org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @Test void getDataById_WhenCalledWithValidUUID_ThenReturnsResource() { @@ -82,9 +85,10 @@ void getAllData_WhenCalledWithValidTypeAndNoFilters_ThenReturnsListOfResources() var resource2 = createTestResource(); resource2.name = "another-resource"; - when(compasPluginsResourceService.list(eq(type), any(), any(), any(), eq(page), eq(size))) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasPluginsResourceService.list(eq(TENANT), eq(type), any(), any(), any(), eq(page), eq(size))) .thenReturn(List.of(resource1, resource2)); - when(compasPluginsResourceService.count(eq(type), any(), any(), any())) + when(compasPluginsResourceService.count(eq(TENANT), eq(type), any(), any(), any())) .thenReturn(2L); given() @@ -112,9 +116,10 @@ void getAllData_WhenCalledWithPaginationParameters_ThenReturnsPagedResponse() { var resource = createTestResource(); - when(compasPluginsResourceService.list(eq(type), any(), any(), any(), eq(page), eq(size))) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasPluginsResourceService.list(eq(TENANT), eq(type), any(), any(), any(), eq(page), eq(size))) .thenReturn(List.of(resource)); - when(compasPluginsResourceService.count(eq(type), any(), any(), any())) + when(compasPluginsResourceService.count(eq(TENANT), eq(type), any(), any(), any())) .thenReturn(6L); // e.g. 6 total entries -> 2 pages of 5 given() @@ -138,9 +143,10 @@ void getAllData_WhenCalledWithNonMatchingFilters_ThenReturnsEmptyContent() { int page = 0; int size = 20; - when(compasPluginsResourceService.list(eq(type), any(), any(), any(), eq(page), eq(size))) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasPluginsResourceService.list(eq(TENANT), eq(type), any(), any(), any(), eq(page), eq(size))) .thenReturn(List.of()); - when(compasPluginsResourceService.count(eq(type), any(), any(), any())) + when(compasPluginsResourceService.count(eq(TENANT), eq(type), any(), any(), any())) .thenReturn(0L); given() diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResourceTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResourceTest.java index 3138bfb6..8ef1c738 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResourceTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasPluginsResourceTest.java @@ -23,18 +23,23 @@ @ExtendWith(MockitoExtension.class) class CompasPluginsResourceTest { + private static final String TENANT = "test-tenant"; @Mock CompasPluginsResourceService service; + @Mock + org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @InjectMocks CompasPluginsResource resource; @Test void getAllData_WhenSizeIsZero_ThenTotalPagesIsZero() { - when(service.list(eq("xml"), isNull(), isNull(), isNull(), eq(0), eq(0))) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(service.list(eq(TENANT), eq("xml"), isNull(), isNull(), isNull(), eq(0), eq(0))) .thenReturn(List.of()); - when(service.count(eq("xml"), isNull(), isNull(), isNull())) + when(service.count(eq(TENANT), eq("xml"), isNull(), isNull(), isNull())) .thenReturn(5L); var response = resource.getAllData("xml", null, null, null, 0, 0); diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResourceAsEditorTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResourceAsEditorTest.java index f918e7bd..2fd6c73e 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResourceAsEditorTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResourceAsEditorTest.java @@ -40,10 +40,14 @@ }) class CompasSclDataResourceAsEditorTest { public static final String USERNAME = "Test Editor"; + private static final String TENANT = "test-tenant"; @InjectMock private CompasSclDataService compasSclDataService; + @InjectMock + private org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @Test void list_WhenCalled_ThenItemResponseRetrieved() { var type = SclFileType.SCD; @@ -52,7 +56,8 @@ void list_WhenCalled_ThenItemResponseRetrieved() { var version = "1.0.0"; var labels = List.of("Label1"); - when(compasSclDataService.list(type)) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.list(TENANT, type)) .thenReturn(Collections.singletonList(new Item(uuid.toString(), name, version, labels))); var response = given() @@ -68,7 +73,7 @@ void list_WhenCalled_ThenItemResponseRetrieved() { assertEquals(name, xmlPath.get("ListResponse.Item[0].Name")); assertEquals(version, xmlPath.get("ListResponse.Item[0].Version")); assertEquals(labels.get(0), xmlPath.get("ListResponse.Item[0].Label")); - verify(compasSclDataService).list(type); + verify(compasSclDataService).list(TENANT, type); } @Test @@ -78,7 +83,8 @@ void listVersionsByUUID_WhenCalled_ThenItemResponseRetrieved() { var name = "Name"; var version = "1.0.0"; - when(compasSclDataService.listVersionsByUUID(type, uuid)) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.listVersionsByUUID(TENANT, type, uuid)) .thenReturn(Collections.singletonList(new HistoryItem(uuid.toString(), name, version, null, null, null))); var response = given() @@ -94,7 +100,7 @@ void listVersionsByUUID_WhenCalled_ThenItemResponseRetrieved() { assertEquals(uuid.toString(), xmlPath.get("VersionsResponse.HistoryItem[0].Id")); assertEquals(name, xmlPath.get("VersionsResponse.HistoryItem[0].Name")); assertEquals(version, xmlPath.get("VersionsResponse.HistoryItem[0].Version")); - verify(compasSclDataService).listVersionsByUUID(type, uuid); + verify(compasSclDataService).listVersionsByUUID(TENANT, type, uuid); } @Test @@ -103,7 +109,8 @@ void findByUUID_WhenCalled_ThenSCLResponseRetrieved() throws IOException { var uuid = UUID.randomUUID(); var scl = readSCL(); - when(compasSclDataService.findByUUID(type, uuid)).thenReturn(scl); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.findByUUID(TENANT, type, uuid)).thenReturn(scl); var response = given() .pathParam(TYPE_PATH_PARAM, type) @@ -115,7 +122,7 @@ void findByUUID_WhenCalled_ThenSCLResponseRetrieved() throws IOException { .response(); assertEquals(scl, response.xmlPath().getString("GetResponse.SclData")); - verify(compasSclDataService).findByUUID(type, uuid); + verify(compasSclDataService).findByUUID(TENANT, type, uuid); } @Test @@ -125,7 +132,8 @@ void findByUUIDAndVersion_WhenCalled_ThenSCLResponseRetrieved() throws IOExcepti var scl = readSCL(); var version = new Version(1, 2, 3); - when(compasSclDataService.findByUUID(type, uuid, version)).thenReturn(scl); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.findByUUID(TENANT, type, uuid, version)).thenReturn(scl); var response = given() .pathParam(TYPE_PATH_PARAM, type) @@ -138,7 +146,7 @@ void findByUUIDAndVersion_WhenCalled_ThenSCLResponseRetrieved() throws IOExcepti .response(); assertEquals(scl, response.xmlPath().getString("GetResponse.SclData")); - verify(compasSclDataService).findByUUID(type, uuid, version); + verify(compasSclDataService).findByUUID(TENANT, type, uuid, version); } @Test @@ -153,7 +161,8 @@ void create_WhenCalled_ThenServiceCalledAndUUIDRetrieved() throws IOException { request.setComment(comment); request.setSclData(scl); - when(compasSclDataService.create(type, name, USERNAME, comment, scl)).thenReturn(scl); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.create(TENANT, type, name, USERNAME, comment, scl)).thenReturn(scl); var response = given() .pathParam(TYPE_PATH_PARAM, type) @@ -166,7 +175,7 @@ void create_WhenCalled_ThenServiceCalledAndUUIDRetrieved() throws IOException { .response(); assertEquals(scl, response.xmlPath().getString("CreateResponse.SclData")); - verify(compasSclDataService).create(type, name, USERNAME, comment, scl); + verify(compasSclDataService).create(TENANT, type, name, USERNAME, comment, scl); } @Test @@ -209,7 +218,8 @@ void update_WhenCalled_ThenServiceCalledAndNewUUIDRetrieved() throws IOException request.setComment(comment); request.setSclData(scl); - when(compasSclDataService.update(type, uuid, changeSetType, USERNAME, comment, scl)).thenReturn(scl); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.update(TENANT, type, uuid, changeSetType, USERNAME, comment, scl)).thenReturn(scl); var response = given() .pathParam(TYPE_PATH_PARAM, type) @@ -223,7 +233,7 @@ void update_WhenCalled_ThenServiceCalledAndNewUUIDRetrieved() throws IOException .response(); assertEquals(scl, response.xmlPath().getString("UpdateResponse.SclData")); - verify(compasSclDataService).update(type, uuid, changeSetType, USERNAME, comment, scl); + verify(compasSclDataService).update(TENANT, type, uuid, changeSetType, USERNAME, comment, scl); } @Test @@ -231,7 +241,8 @@ void deleteAll_WhenCalled_ThenServiceCalled() { var uuid = UUID.randomUUID(); var type = SclFileType.SCD; - doNothing().when(compasSclDataService).delete(type, uuid); + when(tenantService.resolveTenant()).thenReturn(TENANT); + doNothing().when(compasSclDataService).delete(TENANT, type, uuid); given() .pathParam(TYPE_PATH_PARAM, type) @@ -240,7 +251,7 @@ void deleteAll_WhenCalled_ThenServiceCalled() { .then() .statusCode(204); - verify(compasSclDataService).delete(type, uuid); + verify(compasSclDataService).delete(TENANT, type, uuid); } @Test @@ -249,7 +260,8 @@ void deleteVersion_WhenCalled_ThenServiceCalled() { var type = SclFileType.SCD; var version = new Version(1, 2, 3); - doNothing().when(compasSclDataService).delete(type, uuid, version); + when(tenantService.resolveTenant()).thenReturn(TENANT); + doNothing().when(compasSclDataService).delete(TENANT, type, uuid, version); given() .pathParam(TYPE_PATH_PARAM, type) @@ -259,7 +271,7 @@ void deleteVersion_WhenCalled_ThenServiceCalled() { .then() .statusCode(204); - verify(compasSclDataService).delete(type, uuid, version); + verify(compasSclDataService).delete(TENANT, type, uuid, version); } @Test @@ -267,7 +279,8 @@ void checkNameForDuplication_WhenCalled_WithDuplicateName_ThenServiceCalled() { var type = SclFileType.SCD; var name = "STATION-0012312"; - when(compasSclDataService.hasDuplicateSclName(type, name)).thenReturn(true); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.hasDuplicateSclName(TENANT, type, name)).thenReturn(true); var request = new DuplicateNameCheckRequest(); request.setName(name); @@ -282,7 +295,7 @@ void checkNameForDuplication_WhenCalled_WithDuplicateName_ThenServiceCalled() { .extract() .response(); - verify(compasSclDataService).hasDuplicateSclName(type, name); + verify(compasSclDataService).hasDuplicateSclName(TENANT, type, name); assertTrue(response.xmlPath().getBoolean("DuplicateNameCheckResponse.Duplicate")); } @@ -291,7 +304,8 @@ void checkNameForDuplication_WhenCalled_WithUniqueName_ThenServiceCalled() { var type = SclFileType.SCD; var name = "STATION-0012312"; - when(compasSclDataService.hasDuplicateSclName(type, name)).thenReturn(false); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.hasDuplicateSclName(TENANT, type, name)).thenReturn(false); var request = new DuplicateNameCheckRequest(); request.setName(name); @@ -306,7 +320,7 @@ void checkNameForDuplication_WhenCalled_WithUniqueName_ThenServiceCalled() { .extract() .response(); - verify(compasSclDataService).hasDuplicateSclName(type, name); + verify(compasSclDataService).hasDuplicateSclName(TENANT, type, name); assertFalse(response.xmlPath().getBoolean("DuplicateNameCheckResponse.Duplicate")); } diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResourceAsReaderTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResourceAsReaderTest.java index 3094631a..69f25177 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResourceAsReaderTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/rest/v1/CompasSclDataResourceAsReaderTest.java @@ -27,15 +27,20 @@ import static org.lfenergy.compas.scl.data.rest.Constants.*; import static org.mockito.Mockito.*; import io.quarkus.test.InjectMock; +import org.lfenergy.compas.scl.data.rest.TenantService; @QuarkusTest @TestHTTPEndpoint(CompasSclDataResource.class) @TestSecurity(user = "test-reader", roles = {"SCD_" + READ_ROLE}) class CompasSclDataResourceAsReaderTest { + private static final String TENANT = "test-tenant"; @InjectMock private CompasSclDataService compasSclDataService; + @InjectMock + private TenantService tenantService; + @Test void list_WhenCalled_ThenItemResponseRetrieved() { var type = SclFileType.SCD; @@ -44,7 +49,8 @@ void list_WhenCalled_ThenItemResponseRetrieved() { var version = "1.0.0"; var labels = List.of("Label1"); - when(compasSclDataService.list(type)) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.list(TENANT, type)) .thenReturn(Collections.singletonList(new Item(uuid.toString(), name, version, labels))); var response = given() @@ -60,7 +66,7 @@ void list_WhenCalled_ThenItemResponseRetrieved() { assertEquals(name, xmlPath.get("ListResponse.Item[0].Name")); assertEquals(version, xmlPath.get("ListResponse.Item[0].Version")); assertEquals(labels.get(0), xmlPath.get("ListResponse.Item[0].Label")); - verify(compasSclDataService).list(type); + verify(compasSclDataService).list(TENANT, type); } @Test @@ -70,7 +76,8 @@ void listVersionsByUUID_WhenCalled_ThenItemResponseRetrieved() { var name = "Name"; var version = "1.0.0"; - when(compasSclDataService.listVersionsByUUID(type, uuid)) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.listVersionsByUUID(TENANT, type, uuid)) .thenReturn(Collections.singletonList(new HistoryItem(uuid.toString(), name, version, null, null, null))); var response = given() @@ -86,7 +93,7 @@ void listVersionsByUUID_WhenCalled_ThenItemResponseRetrieved() { assertEquals(uuid.toString(), xmlPath.get("VersionsResponse.HistoryItem[0].Id")); assertEquals(name, xmlPath.get("VersionsResponse.HistoryItem[0].Name")); assertEquals(version, xmlPath.get("VersionsResponse.HistoryItem[0].Version")); - verify(compasSclDataService).listVersionsByUUID(type, uuid); + verify(compasSclDataService).listVersionsByUUID(TENANT, type, uuid); } @Test @@ -95,7 +102,8 @@ void findByUUID_WhenCalled_ThenSCLResponseRetrieved() throws IOException { var uuid = UUID.randomUUID(); var scl = readSCL(); - when(compasSclDataService.findByUUID(type, uuid)).thenReturn(scl); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.findByUUID(TENANT, type, uuid)).thenReturn(scl); var response = given() .pathParam(TYPE_PATH_PARAM, type) @@ -107,7 +115,7 @@ void findByUUID_WhenCalled_ThenSCLResponseRetrieved() throws IOException { .response(); assertEquals(scl, response.xmlPath().getString("GetResponse.SclData")); - verify(compasSclDataService).findByUUID(type, uuid); + verify(compasSclDataService).findByUUID(TENANT, type, uuid); } @Test @@ -117,7 +125,8 @@ void findByUUIDAndVersion_WhenCalled_ThenSCLResponseRetrieved() throws IOExcepti var scl = readSCL(); var version = new Version(1, 2, 3); - when(compasSclDataService.findByUUID(type, uuid, version)).thenReturn(scl); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(compasSclDataService.findByUUID(TENANT, type, uuid, version)).thenReturn(scl); var response = given() .pathParam(TYPE_PATH_PARAM, type) @@ -130,7 +139,7 @@ void findByUUIDAndVersion_WhenCalled_ThenSCLResponseRetrieved() throws IOExcepti .response(); assertEquals(scl, response.xmlPath().getString("GetResponse.SclData")); - verify(compasSclDataService).findByUUID(type, uuid, version); + verify(compasSclDataService).findByUUID(TENANT, type, uuid, version); } @Test @@ -186,8 +195,6 @@ void deleteAll_WhenCalled_ThenServiceCalled() { var uuid = UUID.randomUUID(); var type = SclFileType.SCD; - doNothing().when(compasSclDataService).delete(type, uuid); - given() .pathParam(TYPE_PATH_PARAM, type) .pathParam(ID_PATH_PARAM, uuid) @@ -204,8 +211,6 @@ void deleteVersion_WhenCalled_ThenServiceCalled() { var type = SclFileType.SCD; var version = new Version(1, 2, 3); - doNothing().when(compasSclDataService).delete(type, uuid, version); - given() .pathParam(TYPE_PATH_PARAM, type) .pathParam(ID_PATH_PARAM, uuid) diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/service/CompasPluginsResourceServiceTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/service/CompasPluginsResourceServiceTest.java index 3cecebe2..79d4634a 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/service/CompasPluginsResourceServiceTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/service/CompasPluginsResourceServiceTest.java @@ -28,6 +28,7 @@ @ExtendWith(MockitoExtension.class) class CompasPluginsResourceServiceTest { + private static final String TENANT = "test-tenant"; @Mock EntityManager entityManager; @@ -43,7 +44,7 @@ void list_WhenCalledWithTypeOnly_ThenReturnsResults() { var resource = createResource(); when(query.getResultList()).thenReturn(List.of(resource)); - var result = service.list("xml", null, null, null, 0, 20); + var result = service.list(TENANT, "xml", null, null, null, 0, 20); assertEquals(1, result.size()); assertEquals(resource, result.get(0)); @@ -56,7 +57,7 @@ void list_WhenCalledWithAllFilters_ThenAppliesAllFilters() { var query = mockTypedQuery(PluginsCustomResource.class); when(query.getResultList()).thenReturn(List.of()); - service.list("xml", new Date(), new Date(), "test", 1, 10); + service.list(TENANT, "xml", new Date(), new Date(), "test", 1, 10); var jpqlCaptor = ArgumentCaptor.forClass(String.class); verify(entityManager).createQuery(jpqlCaptor.capture(), eq(PluginsCustomResource.class)); @@ -76,7 +77,7 @@ void count_WhenCalledWithTypeOnly_ThenReturnsCount() { var query = mockTypedQuery(Long.class); when(query.getSingleResult()).thenReturn(5L); - var result = service.count("xml", null, null, null); + var result = service.count(TENANT, "xml", null, null, null); assertEquals(5L, result); } @@ -86,7 +87,7 @@ void count_WhenCalledWithFilters_ThenAppliesFilters() { var query = mockTypedQuery(Long.class); when(query.getSingleResult()).thenReturn(3L); - var result = service.count("xml", new Date(), new Date(), "search"); + var result = service.count(TENANT, "xml", new Date(), new Date(), "search"); assertEquals(3L, result); var jpqlCaptor = ArgumentCaptor.forClass(String.class); @@ -126,7 +127,7 @@ void upload_WhenCalledWithExplicitVersion_ThenPersistsEntity() { var duplicateQuery = mockTypedQuery(Long.class); when(duplicateQuery.getSingleResult()).thenReturn(0L); - var result = service.upload(new UploadCustomPluginsResourceData("xml", "name", "application/xml", "", + var result = service.upload(TENANT, new UploadCustomPluginsResourceData("xml", "name", "application/xml", "", "1.0.0", "desc", "2.0.0", null)); verify(entityManager).persist(any(PluginsCustomResource.class)); @@ -137,7 +138,7 @@ void upload_WhenCalledWithExplicitVersion_ThenPersistsEntity() { assertEquals("2.0.0", result.version); assertEquals("1.0.0", result.dataCompatibilityVersion); assertEquals("desc", result.description); - assertEquals("default", result.tenant); + assertEquals(TENANT, result.tenant); } @Test @@ -147,7 +148,7 @@ void upload_WhenDuplicateVersionExists_ThenThrowsCompasDuplicateVersionException var request = new UploadCustomPluginsResourceData("xml", "name", "application/xml", "", "1.0.0", "desc", "2.0.0", null); - assertThrows(CompasDuplicateVersionException.class, () -> service.upload(request)); + assertThrows(CompasDuplicateVersionException.class, () -> service.upload(TENANT, request)); } @ParameterizedTest @@ -161,7 +162,7 @@ void upload_WhenNextVersionType_ThenIncrementsVersion(String nextVersionType, St existing.version = "1.2.3"; when(existingQuery.getResultList()).thenReturn(List.of(existing)); - var result = service.upload(new UploadCustomPluginsResourceData("xml", "name", "application/json", "{}", + var result = service.upload(TENANT, new UploadCustomPluginsResourceData("xml", "name", "application/json", "{}", "1.0.0", "desc", null, nextVersionType)); assertEquals(expectedVersion, result.version); @@ -175,7 +176,7 @@ void upload_WhenNextVersionTypeWithNoExistingVersions_ThenReturns100() { var existingQuery = mockTypedQuery(PluginsCustomResource.class); when(existingQuery.getResultList()).thenReturn(List.of()); - var result = service.upload(new UploadCustomPluginsResourceData("xml", "name", "application/json", "{}", + var result = service.upload(TENANT, new UploadCustomPluginsResourceData("xml", "name", "application/json", "{}", "1.0.0", "desc", null, "MAJOR")); assertEquals("1.0.0", result.version); @@ -185,21 +186,21 @@ void upload_WhenNextVersionTypeWithNoExistingVersions_ThenReturns100() { void upload_WhenInvalidNextVersionType_ThenThrowsCompasInvalidInputException() { var request = new UploadCustomPluginsResourceData("xml", "name", "application/xml", "", "1.0.0", "desc", null, "INVALID"); - assertThrows(CompasInvalidInputException.class, () -> service.upload(request)); + assertThrows(CompasInvalidInputException.class, () -> service.upload(TENANT, request)); } @Test void upload_WhenNoVersionAndNoNextVersionType_ThenThrowsCompasInvalidInputException() { var request = new UploadCustomPluginsResourceData("xml", "name", "application/xml", "", "1.0.0", "desc", null, null); - assertThrows(CompasInvalidInputException.class, () -> service.upload(request)); + assertThrows(CompasInvalidInputException.class, () -> service.upload(TENANT, request)); } @Test void upload_WhenBlankVersionAndBlankNextVersionType_ThenThrowsCompasInvalidInputException() { var request = new UploadCustomPluginsResourceData("xml", "name", "application/xml", "", "1.0.0", "desc", " ", " "); - assertThrows(CompasInvalidInputException.class, () -> service.upload(request)); + assertThrows(CompasInvalidInputException.class, () -> service.upload(TENANT, request)); } // --- validation --- @@ -208,28 +209,28 @@ void upload_WhenBlankVersionAndBlankNextVersionType_ThenThrowsCompasInvalidInput void upload_WhenInvalidContentType_ThenThrowsCompasInvalidInputException() { var request = new UploadCustomPluginsResourceData("xml", "name", "text/plain", "", "1.0.0", "desc", "1.0.0", null); - assertThrows(CompasInvalidInputException.class, () -> service.upload(request)); + assertThrows(CompasInvalidInputException.class, () -> service.upload(TENANT, request)); } @Test void upload_WhenNullContentType_ThenThrowsCompasInvalidInputException() { var request = new UploadCustomPluginsResourceData("xml", "name", null, "", "1.0.0", "desc", "1.0.0", null); - assertThrows(CompasInvalidInputException.class, () -> service.upload(request)); + assertThrows(CompasInvalidInputException.class, () -> service.upload(TENANT, request)); } @Test void upload_WhenInvalidSemverForDataCompatibilityVersion_ThenThrowsCompasInvalidInputException() { var request = new UploadCustomPluginsResourceData("xml", "name", "application/xml", "", "not-a-version", "desc", "1.0.0", null); - assertThrows(CompasInvalidInputException.class, () -> service.upload(request)); + assertThrows(CompasInvalidInputException.class, () -> service.upload(TENANT, request)); } @Test void upload_WhenInvalidSemverForExplicitVersion_ThenThrowsCompasInvalidInputException() { var request = new UploadCustomPluginsResourceData("xml", "name", "application/xml", "", "1.0.0", "desc", "bad", null); - assertThrows(CompasInvalidInputException.class, () -> service.upload(request)); + assertThrows(CompasInvalidInputException.class, () -> service.upload(TENANT, request)); } // --- helpers --- diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/CompasSclDataEventHandlerTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/CompasSclDataEventHandlerTest.java index d937f833..bd829878 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/CompasSclDataEventHandlerTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/CompasSclDataEventHandlerTest.java @@ -36,6 +36,8 @@ @ExtendWith(MockitoExtension.class) class CompasSclDataEventHandlerTest { + private static final String TENANT = "test-tenant"; + @Mock private CompasSclDataService service; @@ -51,14 +53,14 @@ void createWebsocketsEvent_WhenCalled_ThenCreateResponseReturned() { var sclData = "Some SCL Data"; var session = mockSession(); - var request = new CreateEventRequest(session, type, name, who, comment, sclData); - when(service.create(type, name, who, comment, sclData)).thenReturn(sclData); + var request = new CreateEventRequest(session, type, name, who, comment, sclData, TENANT); + when(service.create(TENANT, type, name, who, comment, sclData)).thenReturn(sclData); eventHandler.createWebsocketsEvent(request); var response = verifyResponse(session, CreateWsResponse.class); assertEquals(sclData, response.getSclData()); - verify(service).create(type, name, who, comment, sclData); + verify(service).create(TENANT, type, name, who, comment, sclData); } @Test @@ -71,14 +73,14 @@ void createWebsocketsEvent_WhenCalledAndCompasExceptionThrownByService_ThenError var errorMessage = "Some Error"; var session = mockSession(); - var request = new CreateEventRequest(session, type, name, who, comment, sclData); - when(service.create(type, name, who, comment, sclData)) + var request = new CreateEventRequest(session, type, name, who, comment, sclData, TENANT); + when(service.create(TENANT, type, name, who, comment, sclData)) .thenThrow(new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, errorMessage)); eventHandler.createWebsocketsEvent(request); verifyErrorResponse(session, DUPLICATE_SCL_NAME_ERROR_CODE, errorMessage); - verify(service).create(type, name, who, comment, sclData); + verify(service).create(TENANT, type, name, who, comment, sclData); } @Test @@ -91,13 +93,13 @@ void createWebsocketsEvent_WhenCalledAndRuntimeExceptionThrownByService_ThenErro var errorMessage = "Some Error"; var session = mockSession(); - var request = new CreateEventRequest(session, type, name, who, comment, sclData); - when(service.create(type, name, who, comment, sclData)).thenThrow(new RuntimeException(errorMessage)); + var request = new CreateEventRequest(session, type, name, who, comment, sclData, TENANT); + when(service.create(TENANT, type, name, who, comment, sclData)).thenThrow(new RuntimeException(errorMessage)); eventHandler.createWebsocketsEvent(request); verifyErrorResponse(session, WEBSOCKET_GENERAL_ERROR_CODE, errorMessage); - verify(service).create(type, name, who, comment, sclData); + verify(service).create(TENANT, type, name, who, comment, sclData); } @Test @@ -107,14 +109,14 @@ void getWebsocketsEvent_WhenCalled_ThenGetResponseReturned() { var sclData = "Some SCL Data"; var session = mockSession(); - var request = new GetEventRequest(session, type, id); - when(service.findByUUID(type, id)).thenReturn(sclData); + var request = new GetEventRequest(session, type, id, TENANT); + when(service.findByUUID(TENANT, type, id)).thenReturn(sclData); eventHandler.getWebsocketsEvent(request); var response = verifyResponse(session, GetWsResponse.class); assertEquals(sclData, response.getSclData()); - verify(service).findByUUID(type, id); + verify(service).findByUUID(TENANT, type, id); } @Test @@ -124,14 +126,14 @@ void getWebsocketsEvent_WhenCalledAndCompasExceptionThrownByService_ThenErrorRes var errorMessage = "Some Error"; var session = mockSession(); - var request = new GetEventRequest(session, type, id); - when(service.findByUUID(type, id)) + var request = new GetEventRequest(session, type, id, TENANT); + when(service.findByUUID(TENANT, type, id)) .thenThrow(new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, errorMessage)); eventHandler.getWebsocketsEvent(request); verifyErrorResponse(session, DUPLICATE_SCL_NAME_ERROR_CODE, errorMessage); - verify(service).findByUUID(type, id); + verify(service).findByUUID(TENANT, type, id); } @Test @@ -141,13 +143,13 @@ void getWebsocketsEvent_WhenCalledAndRuntimeExceptionThrownByService_ThenErrorRe var errorMessage = "Some Error"; var session = mockSession(); - var request = new GetEventRequest(session, type, id); - when(service.findByUUID(type, id)).thenThrow(new RuntimeException(errorMessage)); + var request = new GetEventRequest(session, type, id, TENANT); + when(service.findByUUID(TENANT, type, id)).thenThrow(new RuntimeException(errorMessage)); eventHandler.getWebsocketsEvent(request); verifyErrorResponse(session, WEBSOCKET_GENERAL_ERROR_CODE, errorMessage); - verify(service).findByUUID(type, id); + verify(service).findByUUID(TENANT, type, id); } @Test @@ -158,14 +160,14 @@ void getVersionWebsocketsEvent_WhenCalled_ThenGetResponseReturned() { var version = new Version("1.2.3"); var session = mockSession(); - var request = new GetVersionEventRequest(session, type, id, version); - when(service.findByUUID(type, id, version)).thenReturn(sclData); + var request = new GetVersionEventRequest(session, type, id, version, TENANT); + when(service.findByUUID(TENANT, type, id, version)).thenReturn(sclData); eventHandler.getVersionWebsocketsEvent(request); var response = verifyResponse(session, GetWsResponse.class); assertEquals(sclData, response.getSclData()); - verify(service).findByUUID(type, id, version); + verify(service).findByUUID(TENANT, type, id, version); } @Test @@ -176,14 +178,14 @@ void getVersionWebsocketsEvent_WhenCalledAndCompasExceptionThrownByService_ThenE var errorMessage = "Some Error"; var session = mockSession(); - var request = new GetVersionEventRequest(session, type, id, version); - when(service.findByUUID(type, id, version)) + var request = new GetVersionEventRequest(session, type, id, version, TENANT); + when(service.findByUUID(TENANT, type, id, version)) .thenThrow(new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, errorMessage)); eventHandler.getVersionWebsocketsEvent(request); verifyErrorResponse(session, DUPLICATE_SCL_NAME_ERROR_CODE, errorMessage); - verify(service).findByUUID(type, id, version); + verify(service).findByUUID(TENANT, type, id, version); } @Test @@ -194,13 +196,13 @@ void getVersionWebsocketsEvent_WhenCalledAndRuntimeExceptionThrownByService_Then var errorMessage = "Some Error"; var session = mockSession(); - var request = new GetVersionEventRequest(session, type, id, version); - when(service.findByUUID(type, id, version)).thenThrow(new RuntimeException(errorMessage)); + var request = new GetVersionEventRequest(session, type, id, version, TENANT); + when(service.findByUUID(TENANT, type, id, version)).thenThrow(new RuntimeException(errorMessage)); eventHandler.getVersionWebsocketsEvent(request); verifyErrorResponse(session, WEBSOCKET_GENERAL_ERROR_CODE, errorMessage); - verify(service).findByUUID(type, id, version); + verify(service).findByUUID(TENANT, type, id, version); } @Test @@ -213,14 +215,14 @@ void updateWebsocketsEvent_WhenCalled_ThenUpdateResponseReturned() { var sclData = "Some SCL Data"; var session = mockSession(); - var request = new UpdateEventRequest(session, type, id, cst, who, comment, sclData); - when(service.update(type, id, cst, who, comment, sclData)).thenReturn(sclData); + var request = new UpdateEventRequest(session, type, id, cst, who, comment, sclData, TENANT); + when(service.update(TENANT, type, id, cst, who, comment, sclData)).thenReturn(sclData); eventHandler.updateWebsocketsEvent(request); var response = verifyResponse(session, UpdateWsResponse.class); assertEquals(sclData, response.getSclData()); - verify(service).update(type, id, cst, who, comment, sclData); + verify(service).update(TENANT, type, id, cst, who, comment, sclData); } @Test @@ -234,14 +236,14 @@ void updateWebsocketsEvent_WhenCalledAndCompasExceptionThrownByService_ThenError var errorMessage = "Some Error"; var session = mockSession(); - var request = new UpdateEventRequest(session, type, id, cst, who, comment, sclData); - when(service.update(type, id, cst, who, comment, sclData)) + var request = new UpdateEventRequest(session, type, id, cst, who, comment, sclData, TENANT); + when(service.update(TENANT, type, id, cst, who, comment, sclData)) .thenThrow(new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, errorMessage)); eventHandler.updateWebsocketsEvent(request); verifyErrorResponse(session, DUPLICATE_SCL_NAME_ERROR_CODE, errorMessage); - verify(service).update(type, id, cst, who, comment, sclData); + verify(service).update(TENANT, type, id, cst, who, comment, sclData); } @Test @@ -255,13 +257,13 @@ void updateWebsocketsEvent_WhenCalledAndRuntimeExceptionThrownByService_ThenErro var errorMessage = "Some Error"; var session = mockSession(); - var request = new UpdateEventRequest(session, type, id, cst, who, comment, sclData); - when(service.update(type, id, cst, who, comment, sclData)).thenThrow(new RuntimeException(errorMessage)); + var request = new UpdateEventRequest(session, type, id, cst, who, comment, sclData, TENANT); + when(service.update(TENANT, type, id, cst, who, comment, sclData)).thenThrow(new RuntimeException(errorMessage)); eventHandler.updateWebsocketsEvent(request); verifyErrorResponse(session, WEBSOCKET_GENERAL_ERROR_CODE, errorMessage); - verify(service).update(type, id, cst, who, comment, sclData); + verify(service).update(TENANT, type, id, cst, who, comment, sclData); } private Session mockSession() { diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/CreateEventRequestTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/CreateEventRequestTest.java index 60300868..32ef843c 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/CreateEventRequestTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/CreateEventRequestTest.java @@ -25,7 +25,7 @@ void constructor_WhenCalledWith3Arguments_ThenValuesSet() { var comment = "Some comment"; var sclData = "Some SCL Data"; - var result = new CreateEventRequest(session, type, name, who, comment, sclData); + var result = new CreateEventRequest(session, type, name, who, comment, sclData, "test-tenant"); assertEquals(session, result.getSession()); assertEquals(type, result.getType()); @@ -33,6 +33,7 @@ void constructor_WhenCalledWith3Arguments_ThenValuesSet() { assertEquals(who, result.getWho()); assertEquals(comment, result.getComment()); assertEquals(sclData, result.getSclData()); + assertEquals("test-tenant", result.getTenant()); } @Test diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/GetEventRequestTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/GetEventRequestTest.java index 461d72ce..153e378e 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/GetEventRequestTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/GetEventRequestTest.java @@ -23,11 +23,12 @@ void constructor_WhenCalledWith3Arguments_ThenValuesSet() { var type = SclFileType.CID; var id = UUID.randomUUID(); - var result = new GetEventRequest(session, type, id); + var result = new GetEventRequest(session, type, id, "test-tenant"); assertEquals(session, result.getSession()); assertEquals(type, result.getType()); assertEquals(id, result.getId()); + assertEquals("test-tenant", result.getTenant()); } @Test diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/GetVersionEventRequestTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/GetVersionEventRequestTest.java index 0ca96c0a..1b238ce2 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/GetVersionEventRequestTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/GetVersionEventRequestTest.java @@ -25,12 +25,13 @@ void constructor_WhenCalledWith3Arguments_ThenValuesSet() { var id = UUID.randomUUID(); var version = new Version("1.2.3"); - var result = new GetVersionEventRequest(session, type, id, version); + var result = new GetVersionEventRequest(session, type, id, version, "test-tenant"); assertEquals(session, result.getSession()); assertEquals(type, result.getType()); assertEquals(id, result.getId()); assertEquals(version, result.getVersion()); + assertEquals("test-tenant", result.getTenant()); } @Test diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/UpdateEventRequestTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/UpdateEventRequestTest.java index 9c5fa4f4..ddaa64ea 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/UpdateEventRequestTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/event/model/UpdateEventRequestTest.java @@ -28,7 +28,7 @@ void constructor_WhenCalledWith3Arguments_ThenValuesSet() { var comment = "Some comment"; var sclData = "Some SCL Data"; - var result = new UpdateEventRequest(session, type, id, changeSetType, who, comment, sclData); + var result = new UpdateEventRequest(session, type, id, changeSetType, who, comment, sclData, "test-tenant"); assertEquals(session, result.getSession()); assertEquals(type, result.getType()); @@ -37,6 +37,7 @@ void constructor_WhenCalledWith3Arguments_ThenValuesSet() { assertEquals(who, result.getWho()); assertEquals(comment, result.getComment()); assertEquals(sclData, result.getSclData()); + assertEquals("test-tenant", result.getTenant()); } @Test diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpointAsEditorTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpointAsEditorTest.java index bf43e0b0..0b1d1117 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpointAsEditorTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpointAsEditorTest.java @@ -25,9 +25,14 @@ @QuarkusTest class CompasSclCreateServerEndpointAsEditorTest extends AbstractServerEndpointAsEditorTestSupport { + private static final String TENANT = "test-tenant"; + @InjectMock private CompasSclDataService service; + @InjectMock + private org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @TestHTTPResource("/scl-ws/v1/SCD/create") private URI uri; @@ -44,14 +49,15 @@ void createSCL_WhenCalled_ThenExpectedResponseIsRetrieved() throws Exception { request.setComment(comment); request.setSclData(sclData); - when(service.create(sclFileTye, name, USERNAME, comment, sclData)) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(service.create(TENANT, sclFileTye, name, USERNAME, comment, sclData)) .thenReturn(sclData); try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(Client.class, uri)) { session.getAsyncRemote().sendText(encoder.encode(request)); assertSclData(sclData); - verify(service).create(sclFileTye, name, USERNAME, comment, sclData); + verify(service).create(TENANT, sclFileTye, name, USERNAME, comment, sclData); } } diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpointTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpointTest.java index b44f5e67..5fb8de0a 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpointTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclCreateServerEndpointTest.java @@ -18,7 +18,7 @@ class CompasSclCreateServerEndpointTest extends AbstractServerEndpointOnOpenTest @Test void onOpen_WhenSessionCloseThrowsIOException_ThenExceptionIsHandledGracefully() throws IOException { - var endpoint = new CompasSclCreateServerEndpoint(null, jsonWebToken, null); + var endpoint = new CompasSclCreateServerEndpoint(null, jsonWebToken, null, null); testOnOpenWhenSessionCloseThrowsIOException(jsonWebToken, endpoint::onOpen); } } diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpointAsEditorTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpointAsEditorTest.java index 8d1e1bc1..9f4fba27 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpointAsEditorTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpointAsEditorTest.java @@ -26,9 +26,14 @@ @QuarkusTest class CompasSclGetServerEndpointAsEditorTest extends AbstractServerEndpointAsEditorTestSupport { + private static final String TENANT = "test-tenant"; + @InjectMock private CompasSclDataService service; + @InjectMock + private org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @TestHTTPResource("/scl-ws/v1/SCD/get") private URI uri; @@ -42,13 +47,14 @@ void getSCL_WhenCalled_ThenExpectedResponseIsRetrieved() throws Exception { var request = new GetWsRequest(); request.setId(id); - when(service.findByUUID(sclFileTye, id)).thenReturn(sclData); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(service.findByUUID(TENANT, sclFileTye, id)).thenReturn(sclData); try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(Client.class, uri)) { session.getAsyncRemote().sendText(encoder.encode(request)); assertSclData(sclData); - verify(service).findByUUID(sclFileTye, id); + verify(service).findByUUID(TENANT, sclFileTye, id); } } diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpointAsReaderTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpointAsReaderTest.java index 038d453b..c1932491 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpointAsReaderTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetServerEndpointAsReaderTest.java @@ -26,9 +26,14 @@ @QuarkusTest class CompasSclGetServerEndpointAsReaderTest extends AbstractServerEndpointAsReaderTestSupport { + private static final String TENANT = "test-tenant"; + @InjectMock private CompasSclDataService service; + @InjectMock + private org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @TestHTTPResource("/scl-ws/v1/SCD/get") private URI uri; @@ -42,13 +47,14 @@ void getSCL_WhenCalled_ThenExpectedResponseIsRetrieved() throws Exception { var request = new GetWsRequest(); request.setId(id); - when(service.findByUUID(sclFileTye, id)).thenReturn(sclData); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(service.findByUUID(TENANT, sclFileTye, id)).thenReturn(sclData); try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(Client.class, uri)) { session.getAsyncRemote().sendText(encoder.encode(request)); assertSclData(sclData); - verify(service).findByUUID(sclFileTye, id); + verify(service).findByUUID(TENANT, sclFileTye, id); } } diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpointAsEditorTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpointAsEditorTest.java index 834dd22d..aaee0987 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpointAsEditorTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpointAsEditorTest.java @@ -27,9 +27,14 @@ @QuarkusTest class CompasSclGetVersionServerEndpointAsEditorTest extends AbstractServerEndpointAsEditorTestSupport { + private static final String TENANT = "test-tenant"; + @InjectMock private CompasSclDataService service; + @InjectMock + private org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @TestHTTPResource("/scl-ws/v1/SCD/get-version") private URI uri; @@ -45,13 +50,14 @@ void getVersionSCL_WhenCalled_ThenExpectedResponseIsRetrieved() throws Exception request.setId(id); request.setVersion(version.toString()); - when(service.findByUUID(sclFileTye, id, version)).thenReturn(sclData); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(service.findByUUID(TENANT, sclFileTye, id, version)).thenReturn(sclData); try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(Client.class, uri)) { session.getAsyncRemote().sendText(encoder.encode(request)); assertSclData(sclData); - verify(service).findByUUID(sclFileTye, id, version); + verify(service).findByUUID(TENANT, sclFileTye, id, version); } } diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpointAsReaderTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpointAsReaderTest.java index 903047e7..1cba6af8 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpointAsReaderTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclGetVersionServerEndpointAsReaderTest.java @@ -27,9 +27,14 @@ @QuarkusTest class CompasSclGetVersionServerEndpointAsReaderTest extends AbstractServerEndpointAsReaderTestSupport { + private static final String TENANT = "test-tenant"; + @InjectMock private CompasSclDataService service; + @InjectMock + private org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @TestHTTPResource("/scl-ws/v1/SCD/get-version") private URI uri; @@ -45,13 +50,14 @@ void getVersionSCL_WhenCalled_ThenExpectedResponseIsRetrieved() throws Exception request.setId(id); request.setVersion(version.toString()); - when(service.findByUUID(sclFileTye, id, version)).thenReturn(sclData); + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(service.findByUUID(TENANT, sclFileTye, id, version)).thenReturn(sclData); try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(Client.class, uri)) { session.getAsyncRemote().sendText(encoder.encode(request)); assertSclData(sclData); - verify(service).findByUUID(sclFileTye, id, version); + verify(service).findByUUID(TENANT, sclFileTye, id, version); } } diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpointAsEditorTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpointAsEditorTest.java index 795688b5..203b5a45 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpointAsEditorTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpointAsEditorTest.java @@ -27,9 +27,14 @@ @QuarkusTest class CompasSclUpdateServerEndpointAsEditorTest extends AbstractServerEndpointAsEditorTestSupport { + private static final String TENANT = "test-tenant"; + @InjectMock private CompasSclDataService service; + @InjectMock + private org.lfenergy.compas.scl.data.rest.TenantService tenantService; + @TestHTTPResource("/scl-ws/v1/SCD/update") private URI uri; @@ -48,14 +53,15 @@ void updateSCL_WhenCalled_ThenExpectedResponseIsRetrieved() throws Exception { request.setComment(comment); request.setSclData(sclData); - when(service.update(sclFileTye, id, cst, USERNAME, comment, sclData)) + when(tenantService.resolveTenant()).thenReturn(TENANT); + when(service.update(TENANT, sclFileTye, id, cst, USERNAME, comment, sclData)) .thenReturn(sclData); try (Session session = ContainerProvider.getWebSocketContainer().connectToServer(Client.class, uri)) { session.getAsyncRemote().sendText(encoder.encode(request)); assertSclData(sclData); - verify(service).update(sclFileTye, id, cst, USERNAME, comment, sclData); + verify(service).update(TENANT, sclFileTye, id, cst, USERNAME, comment, sclData); } } diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpointTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpointTest.java index 3fa7f928..815e543f 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpointTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/websocket/v1/CompasSclUpdateServerEndpointTest.java @@ -18,7 +18,7 @@ class CompasSclUpdateServerEndpointTest extends AbstractServerEndpointOnOpenTest @Test void onOpen_WhenSessionCloseThrowsIOException_ThenExceptionIsHandledGracefully() throws IOException { - var endpoint = new CompasSclUpdateServerEndpoint(null, jsonWebToken, null); + var endpoint = new CompasSclUpdateServerEndpoint(null, jsonWebToken, null, null); testOnOpenWhenSessionCloseThrowsIOException(jsonWebToken, endpoint::onOpen); } } diff --git a/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepository.java b/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepository.java index c2081a85..7d89d7c6 100644 --- a/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepository.java +++ b/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/CompasSclDataPostgreSQLRepository.java @@ -40,14 +40,16 @@ public class CompasSclDataPostgreSQLRepository implements CompasSclDataRepositor protected static String DELETE_SCL_FILE_SQL = """ delete from scl_file - where scl_file.id = ? - and scl_file.type = ? + where scl_file.id = ? + and scl_file.type = ? + and scl_file.tenant = ? """; protected static String DELETE_SCL_FILE_SQL_BY_VERSION = """ delete from scl_file - where scl_file.id = ? - and scl_file.type = ? + where scl_file.id = ? + and scl_file.type = ? + and scl_file.tenant = ? and scl_file.major_version = ? and scl_file.minor_version = ? and scl_file.patch_version = ? @@ -59,14 +61,15 @@ public CompasSclDataPostgreSQLRepository(DataSource dataSource) { @Override @Transactional(SUPPORTS) - public List list(SclFileType type) { + public List list(String tenant, SclFileType type) { var sql = """ select scl_file.id, scl_file.name, scl_file.major_version, scl_file.minor_version, scl_file.patch_version, scl_labels.label_values as labels from (select distinct on (scl_file.id) * from scl_file - where scl_file.type = ? + where scl_file.type = ? + and scl_file.tenant = ? and scl_file.is_deleted = false order by scl_file.id , scl_file.major_version desc @@ -89,6 +92,7 @@ left outer join ( try (var connection = dataSource.getConnection(); var stmt = connection.prepareStatement(sql)) { stmt.setString(1, type.name()); + stmt.setString(2, tenant); try (var resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -106,7 +110,7 @@ left outer join ( @Override @Transactional(SUPPORTS) - public List listVersionsByUUID(SclFileType type, UUID id) { + public List listVersionsByUUID(String tenant, SclFileType type, UUID id) { var sql = """ select scl_file.id, scl_file.name , scl_file.major_version, scl_file.minor_version, scl_file.patch_version @@ -125,8 +129,9 @@ left outer join ( and scl_data.major_version = scl_file.major_version and scl_data.minor_version = scl_file.minor_version and scl_data.patch_version = scl_file.patch_version - where scl_file.id = ? - and scl_file.type = ? + where scl_file.id = ? + and scl_file.type = ? + and scl_file.tenant = ? and scl_file.is_deleted = false order by scl_file.major_version , scl_file.minor_version @@ -138,6 +143,7 @@ left outer join ( var stmt = connection.prepareStatement(sql)) { stmt.setObject(1, id); stmt.setString(2, type.name()); + stmt.setString(3, tenant); try (var resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -157,21 +163,22 @@ left outer join ( @Override @Transactional(SUPPORTS) - public String findByUUID(SclFileType type, UUID id) { + public String findByUUID(String tenant, SclFileType type, UUID id) { // Use the find meta info to retrieve info about the latest version. - var metaInfo = findMetaInfoByUUID(type, id); + var metaInfo = findMetaInfoByUUID(tenant, type, id); // Next return the data using the meta info. - return findByUUID(type, id, new Version(metaInfo.getVersion())); + return findByUUID(tenant, type, id, new Version(metaInfo.getVersion())); } @Override @Transactional(SUPPORTS) - public String findByUUID(SclFileType type, UUID id, Version version) { + public String findByUUID(String tenant, SclFileType type, UUID id, Version version) { var sql = """ select scl_file.scl_data from scl_file - where scl_file.id = ? - and scl_file.type = ? + where scl_file.id = ? + and scl_file.type = ? + and scl_file.tenant = ? and scl_file.major_version = ? and scl_file.minor_version = ? and scl_file.patch_version = ? @@ -182,9 +189,10 @@ public String findByUUID(SclFileType type, UUID id, Version version) { var stmt = connection.prepareStatement(sql)) { stmt.setObject(1, id); stmt.setString(2, type.name()); - stmt.setInt(3, version.getMajorVersion()); - stmt.setInt(4, version.getMinorVersion()); - stmt.setInt(5, version.getPatchVersion()); + stmt.setString(3, tenant); + stmt.setInt(4, version.getMajorVersion()); + stmt.setInt(5, version.getMinorVersion()); + stmt.setInt(6, version.getPatchVersion()); try (var resultSet = stmt.executeQuery()) { if (resultSet.next()) { @@ -200,11 +208,12 @@ public String findByUUID(SclFileType type, UUID id, Version version) { @Override @Transactional(SUPPORTS) - public boolean hasDuplicateSclName(SclFileType type, String name) { + public boolean hasDuplicateSclName(String tenant, SclFileType type, String name) { var sql = """ select distinct on (scl_file.id) scl_file.name from scl_file - where scl_file.type = ? + where scl_file.type = ? + and scl_file.tenant = ? and scl_file.is_deleted = false order by scl_file.id , scl_file.major_version desc @@ -215,6 +224,7 @@ select distinct on (scl_file.id) scl_file.name try (var connection = dataSource.getConnection(); var stmt = connection.prepareStatement(sql)) { stmt.setString(1, type.name()); + stmt.setString(2, tenant); try (var resultSet = stmt.executeQuery()) { while (resultSet.next()) { @@ -230,12 +240,13 @@ select distinct on (scl_file.id) scl_file.name @Override @Transactional(SUPPORTS) - public IAbstractItem findMetaInfoByUUID(SclFileType type, UUID id) { + public IAbstractItem findMetaInfoByUUID(String tenant, SclFileType type, UUID id) { var sql = """ select scl_file.id, scl_file.name, scl_file.major_version, scl_file.minor_version, scl_file.patch_version from scl_file - where scl_file.id = ? - and scl_file.type = ? + where scl_file.id = ? + and scl_file.type = ? + and scl_file.tenant = ? and scl_file.is_deleted = false order by scl_file.major_version desc, scl_file.minor_version desc, scl_file.patch_version desc """; @@ -244,6 +255,7 @@ public IAbstractItem findMetaInfoByUUID(SclFileType type, UUID id) { var stmt = connection.prepareStatement(sql)) { stmt.setObject(1, id); stmt.setString(2, type.name()); + stmt.setString(3, tenant); try (var resultSet = stmt.executeQuery()) { // We need to only retrieve the first row, because that's the latest version. @@ -262,10 +274,10 @@ public IAbstractItem findMetaInfoByUUID(SclFileType type, UUID id) { @Override @Transactional(REQUIRED) - public void create(SclFileType type, UUID id, String name, String scl, Version version, String who, List labels) { + public void create(String tenant, SclFileType type, UUID id, String name, String scl, Version version, String who, List labels) { var createSclSQL = """ - insert into scl_file(id, major_version, minor_version, patch_version, type, name, created_by, scl_data) - values (?, ?, ?, ?, ?, ?, ?, ?) + insert into scl_file(id, major_version, minor_version, patch_version, type, tenant, name, created_by, scl_data) + values (?, ?, ?, ?, ?, ?, ?, ?, ?) """; try (var connection = dataSource.getConnection(); @@ -276,9 +288,10 @@ insert into scl_file(id, major_version, minor_version, patch_version, type, name sclStmt.setInt(3, version.getMinorVersion()); sclStmt.setInt(4, version.getPatchVersion()); sclStmt.setString(5, type.name()); - sclStmt.setString(6, name); - sclStmt.setString(7, who); - sclStmt.setString(8, scl); + sclStmt.setString(6, tenant); + sclStmt.setString(7, name); + sclStmt.setString(8, who); + sclStmt.setString(9, scl); sclStmt.executeUpdate(); // Add the label to the database, if there are any. @@ -319,12 +332,13 @@ insert into scl_label(scl_id, major_version, minor_version, patch_version, label @Override @Transactional(REQUIRED) - public void delete(SclFileType type, UUID id) { + public void delete(String tenant, SclFileType type, UUID id) { try (var connection = dataSource.getConnection(); var stmt = connection.prepareStatement(DELETE_SCL_FILE_SQL)) { stmt.setObject(1, id); stmt.setString(2, type.name()); + stmt.setString(3, tenant); stmt.executeUpdate(); } catch (SQLException exp) { throw new CompasSclDataServiceException(POSTGRES_DELETE_ERROR_CODE, "Error removing SCL from database!", exp); @@ -333,15 +347,16 @@ public void delete(SclFileType type, UUID id) { @Override @Transactional(REQUIRED) - public void delete(SclFileType type, UUID id, Version version) { + public void delete(String tenant, SclFileType type, UUID id, Version version) { try (var connection = dataSource.getConnection(); var stmt = connection.prepareStatement(DELETE_SCL_FILE_SQL_BY_VERSION)) { stmt.setObject(1, id); stmt.setString(2, type.name()); - stmt.setInt(3, version.getMajorVersion()); - stmt.setInt(4, version.getMinorVersion()); - stmt.setInt(5, version.getPatchVersion()); + stmt.setString(3, tenant); + stmt.setInt(4, version.getMajorVersion()); + stmt.setInt(5, version.getMinorVersion()); + stmt.setInt(6, version.getPatchVersion()); stmt.executeUpdate(); } catch (SQLException exp) { throw new CompasSclDataServiceException(POSTGRES_DELETE_ERROR_CODE, "Error removing SCL (version) from database!", exp); diff --git a/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/SoftDeleteCompasSclDataPostgreSQLRepository.java b/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/SoftDeleteCompasSclDataPostgreSQLRepository.java index 172b3019..65255148 100644 --- a/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/SoftDeleteCompasSclDataPostgreSQLRepository.java +++ b/repository-postgresql/src/main/java/org/lfenergy/compas/scl/data/repository/postgresql/SoftDeleteCompasSclDataPostgreSQLRepository.java @@ -11,14 +11,16 @@ public class SoftDeleteCompasSclDataPostgreSQLRepository extends CompasSclDataPo private static final String SOFT_DELETE_SCL_FILE_SQL = """ update scl_file set is_deleted = true - where scl_file.id = ? - and scl_file.type = ? + where scl_file.id = ? + and scl_file.type = ? + and scl_file.tenant = ? """; private static final String SOFT_DELETE_SCL_FILE_SQL_BY_VERSION = """ delete from scl_file - where scl_file.id = ? - and scl_file.type = ? + where scl_file.id = ? + and scl_file.type = ? + and scl_file.tenant = ? and scl_file.major_version = ? and scl_file.minor_version = ? and scl_file.patch_version = ? diff --git a/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_8__add_tenant_to_scl_file.sql b/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_8__add_tenant_to_scl_file.sql new file mode 100644 index 00000000..bdadfc94 --- /dev/null +++ b/repository-postgresql/src/main/resources/org/lfenergy/compas/scl/data/repository/postgresql/db/migration/V1_8__add_tenant_to_scl_file.sql @@ -0,0 +1,16 @@ +/** + * SPDX-FileCopyrightText: 2026 Alliander N.V. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +-- +-- Add tenant column to scl_file for multi-tenant data isolation. +-- Existing rows default to 'global' (the no-auth default tenant). +-- +ALTER TABLE scl_file + ADD COLUMN tenant varchar(255) not null default 'global'; + +CREATE INDEX scl_file_tenant ON scl_file(tenant); + +COMMENT ON COLUMN scl_file.tenant IS 'Tenant identifier: ''global'' when auth is disabled, OIDC realm name when auth is enabled.'; diff --git a/repository/src/main/java/org/lfenergy/compas/scl/data/SclDataServiceConstants.java b/repository/src/main/java/org/lfenergy/compas/scl/data/SclDataServiceConstants.java index ce1f7951..cea92a04 100644 --- a/repository/src/main/java/org/lfenergy/compas/scl/data/SclDataServiceConstants.java +++ b/repository/src/main/java/org/lfenergy/compas/scl/data/SclDataServiceConstants.java @@ -10,6 +10,11 @@ public class SclDataServiceConstants { public static final String SCL_DATA_SERVICE_V1_NS_URI = "https://www.lfenergy.org/compas/SclDataService/v1"; + /** + * Fallback tenant name used when authentication is disabled. + */ + public static final String GLOBAL_TENANT = "global"; + public static final String SCL_NS_URI = "http://www.iec.ch/61850/2003/SCL"; public static final String SCL_NS_PREFIX = ""; public static final String SCL_ELEMENT_NAME = "SCL"; diff --git a/repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java b/repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java index 4535fdda..826ea822 100644 --- a/repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java +++ b/repository/src/main/java/org/lfenergy/compas/scl/data/repository/CompasSclDataRepository.java @@ -17,56 +17,62 @@ public interface CompasSclDataRepository { /** * List the latest version of all SCL Entries for a type of SCL. * - * @param type The type of SCL to search for. + * @param tenant The tenant to scope the data to. + * @param type The type of SCL to search for. * @return The list of entries found for the passed type. */ - List list(SclFileType type); + List list(String tenant, SclFileType type); /** * List all versions for a specific SCL Entry for a type of SCL. * - * @param type The type of SCL to search for the specific SCL. - * @param id The ID of the SCL to search for. + * @param tenant The tenant to scope the data to. + * @param type The type of SCL to search for the specific SCL. + * @param id The ID of the SCL to search for. * @return The list of versions found for that specific sCl Entry. */ - List listVersionsByUUID(SclFileType type, UUID id); + List listVersionsByUUID(String tenant, SclFileType type, UUID id); /** * Return the latest version of a specific SCL Entry. * - * @param type The type of SCL to search for the specific SCL. - * @param id The ID of the SCL to search for. + * @param tenant The tenant to scope the data to. + * @param type The type of SCL to search for the specific SCL. + * @param id The ID of the SCL to search for. * @return The SCL XML File Content that is search for. */ - String findByUUID(SclFileType type, UUID id); + String findByUUID(String tenant, SclFileType type, UUID id); /** * Return the meta info of the latest version of a specific SCL Entry. * - * @param type The type of SCL to search for the specific SCL. - * @param id The ID of the SCL to search for. + * @param tenant The tenant to scope the data to. + * @param type The type of SCL to search for the specific SCL. + * @param id The ID of the SCL to search for. * @return The Meta Info of SCL Entry that is search for. */ - IAbstractItem findMetaInfoByUUID(SclFileType type, UUID id); + IAbstractItem findMetaInfoByUUID(String tenant, SclFileType type, UUID id); /** * Return the specific version of a specific SCL Entry. * + * @param tenant The tenant to scope the data to. * @param type The type of SCL to search for the specific SCL. * @param id The ID of the SCL to search for. * @param version The version of the ScL to search for. * @return The SCL XML File Content that is search for. */ - String findByUUID(SclFileType type, UUID id, Version version); + String findByUUID(String tenant, SclFileType type, UUID id, Version version); /** - * Return the specific version of a specific SCL Entry. + * Check whether the name is already used by another SCL File of the same type within the tenant. * - * @param type The type of SCL to search for the specific SCL. - * @param name The name of the SCL used for checking duplicates. + * @param tenant The tenant to scope the data to. + * @param type The type of SCL used for checking duplicates. + * @param name The name of the SCL used for checking duplicates. * @return True if name is already used by another SCL File of the same File type, otherwise false. */ - boolean hasDuplicateSclName(SclFileType type, String name); + boolean hasDuplicateSclName(String tenant, SclFileType type, String name); /** * Create a new entry for the passed UUID with the version number passed. @@ -75,6 +81,7 @@ public interface CompasSclDataRepository { * When a entry is updated the service layer will increase the version and always create a new entry * in the repository. * + * @param tenant The tenant to scope the data to. * @param type The type of SCL to store it in. * @param id The ID of the new entry to be created. * @param name The name of the SCL to be stored. @@ -83,22 +90,24 @@ public interface CompasSclDataRepository { * @param who The user that created the new entry. * @param labels The list of Labels extracted from the SCL XML File. */ - void create(SclFileType type, UUID id, String name, String scl, Version version, String who, List labels); + void create(String tenant, SclFileType type, UUID id, String name, String scl, Version version, String who, List labels); /** * Delete all versions for a specific SCL File using its ID. * - * @param type The type of SCL where to find the SCL File - * @param id The ID of the SCL File to delete. + * @param tenant The tenant to scope the data to. + * @param type The type of SCL where to find the SCL File + * @param id The ID of the SCL File to delete. */ - void delete(SclFileType type, UUID id); + void delete(String tenant, SclFileType type, UUID id); /** * Delete passed versions for a specific SCL File using its ID. * + * @param tenant The tenant to scope the data to. * @param type The type of SCL where to find the SCL File * @param id The ID of the SCL File to delete. * @param version The version of that SCL File to delete. */ - void delete(SclFileType type, UUID id, Version version); + void delete(String tenant, SclFileType type, UUID id, Version version); } diff --git a/repository/src/test/java/org/lfenergy/compas/scl/data/repository/AbstractCompasSclDataRepositoryTest.java b/repository/src/test/java/org/lfenergy/compas/scl/data/repository/AbstractCompasSclDataRepositoryTest.java index 0f20e022..7bf900a0 100644 --- a/repository/src/test/java/org/lfenergy/compas/scl/data/repository/AbstractCompasSclDataRepositoryTest.java +++ b/repository/src/test/java/org/lfenergy/compas/scl/data/repository/AbstractCompasSclDataRepositoryTest.java @@ -15,6 +15,7 @@ import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.NO_DATA_FOUND_ERROR_CODE; public abstract class AbstractCompasSclDataRepositoryTest { + protected static final String TENANT = "test-tenant"; protected static final SclFileType TYPE = SclFileType.SCD; // Use different types, so tests don't conflict with each other. @@ -23,7 +24,7 @@ public abstract class AbstractCompasSclDataRepositoryTest { @Test void list_WhenCalledOnEmptyDatabase_ThenNoRecordsReturned() { - var items = getRepository().list(LIST1_TYPE); + var items = getRepository().list(TENANT, LIST1_TYPE); assertNotNull(items); assertEquals(0, items.size()); @@ -33,7 +34,7 @@ void list_WhenCalledOnEmptyDatabase_ThenNoRecordsReturned() { void listVersionsByUUID_WhenCalledWithUnknownID_ThenExceptionIsThrown() { var uuid = UUID.randomUUID(); - var items = getRepository().listVersionsByUUID(TYPE, uuid); + var items = getRepository().listVersionsByUUID(TENANT, TYPE, uuid); assertNotNull(items); assertEquals(0, items.size()); @@ -45,7 +46,7 @@ void findByUUID_WhenCalledWithUnknownUUID_ThenExceptionIsThrown() { var repository = getRepository(); var exception = assertThrows(CompasNoDataFoundException.class, - () -> repository.findByUUID(TYPE, uuid)); + () -> repository.findByUUID(TENANT, TYPE, uuid)); assertEquals(NO_DATA_FOUND_ERROR_CODE, exception.getErrorCode()); } @@ -55,7 +56,7 @@ void findMetaInfoByUUID_WhenCalledWithUnknownUUID_ThenExceptionIsThrown() { var repository = getRepository(); var exception = assertThrows(CompasNoDataFoundException.class, - () -> repository.findMetaInfoByUUID(TYPE, uuid)); + () -> repository.findMetaInfoByUUID(TENANT, TYPE, uuid)); assertEquals(NO_DATA_FOUND_ERROR_CODE, exception.getErrorCode()); } @@ -66,12 +67,12 @@ void delete_AllVersions_SoftDeleted() { Version version2 = new Version(2, 0, 0); var repository = getRepository(); - repository.create(TYPE, uuid, "TestName", "", version1, "tester", List.of("label")); - repository.create(TYPE, uuid, "TestName", "", version2, "tester", List.of("label")); + repository.create(TENANT, TYPE, uuid, "TestName", "", version1, "tester", List.of("label")); + repository.create(TENANT, TYPE, uuid, "TestName", "", version2, "tester", List.of("label")); - repository.delete(TYPE, uuid); + repository.delete(TENANT, TYPE, uuid); - var items = repository.listVersionsByUUID(TYPE, uuid); + var items = repository.listVersionsByUUID(TENANT, TYPE, uuid); assertEquals(0, items.size(), "All versions should be soft deleted"); } @@ -82,12 +83,12 @@ void delete_SpecificVersion_Deleted() { Version version2 = new Version(2, 0, 0); var repository = getRepository(); - repository.create(TYPE, uuid, "TestName", "", version1, "tester", List.of("label")); - repository.create(TYPE, uuid, "TestName", "", version2, "tester", List.of("label")); + repository.create(TENANT, TYPE, uuid, "TestName", "", version1, "tester", List.of("label")); + repository.create(TENANT, TYPE, uuid, "TestName", "", version2, "tester", List.of("label")); - repository.delete(TYPE, uuid, version1); + repository.delete(TENANT, TYPE, uuid, version1); - var items = repository.listVersionsByUUID(TYPE, uuid); + var items = repository.listVersionsByUUID(TENANT, TYPE, uuid); assertEquals(1, items.size(), "Only one version should remain"); assertEquals(version2.toString(), items.get(0).getVersion(), "Remaining version should be version2"); } diff --git a/service/src/main/java/org/lfenergy/compas/scl/data/service/CompasSclDataService.java b/service/src/main/java/org/lfenergy/compas/scl/data/service/CompasSclDataService.java index dbbccd31..14f69cce 100644 --- a/service/src/main/java/org/lfenergy/compas/scl/data/service/CompasSclDataService.java +++ b/service/src/main/java/org/lfenergy/compas/scl/data/service/CompasSclDataService.java @@ -55,12 +55,13 @@ public CompasSclDataService(CompasSclDataRepository repository, ElementConverter /** * List the latest version of all SCL XML Files for a specific type. * - * @param type The type to search for. + * @param tenant The tenant to scope the data to. + * @param type The type to search for. * @return The List of Items found. */ @Transactional(SUPPORTS) - public List list(SclFileType type) { - return repository.list(type) + public List list(String tenant, SclFileType type) { + return repository.list(tenant, type) .stream() .map(e -> new Item(e.getId(), e.getName(), e.getVersion(), e.getLabels())) .toList(); @@ -69,13 +70,14 @@ public List list(SclFileType type) { /** * Search for all versions of a specific SCL XML File (using the UUID) for a specific type. * - * @param type The type to search for. - * @param id The UUID of the record to search for. + * @param tenant The tenant to scope the data to. + * @param type The type to search for. + * @param id The UUID of the record to search for. * @return The list of versions found. */ @Transactional(SUPPORTS) - public List listVersionsByUUID(SclFileType type, UUID id) { - var items = repository.listVersionsByUUID(type, id); + public List listVersionsByUUID(String tenant, SclFileType type, UUID id) { + var items = repository.listVersionsByUUID(tenant, type, id); if (items.isEmpty()) { var message = String.format("No versions found for type '%s' with ID '%s'", type, id); throw new CompasNoDataFoundException(message); @@ -89,32 +91,35 @@ public List listVersionsByUUID(SclFileType type, UUID id) { /** * Get the latest version of a specific SCL XML File (using the UUID) for a specific type. * - * @param type The type to search for. - * @param id The UUID of the record to search for. + * @param tenant The tenant to scope the data to. + * @param type The type to search for. + * @param id The UUID of the record to search for. * @return The latest version of the SCL XML Files. */ @Transactional(SUPPORTS) - public String findByUUID(SclFileType type, UUID id) { - return repository.findByUUID(type, id); + public String findByUUID(String tenant, SclFileType type, UUID id) { + return repository.findByUUID(tenant, type, id); } /** * Get a specific version of a specific SCL XML File (using the UUID) for a specific type. * + * @param tenant The tenant to scope the data to. * @param type The type to search for. * @param id The UUID of the record to search for. * @param version The version to search for. * @return The found version of the SCL XML Files. */ @Transactional(SUPPORTS) - public String findByUUID(SclFileType type, UUID id, Version version) { - return repository.findByUUID(type, id, version); + public String findByUUID(String tenant, SclFileType type, UUID id, Version version) { + return repository.findByUUID(tenant, type, id, version); } /** * Create a new record for the passed SCL XML File with the passed name for a specific type. * A new UUID is generated to be set and also the CoMPAS Private Elements are added on SCL Level. * + * @param tenant The tenant to scope the data to. * @param type The type to create it for. * @param name The name that will be stored as CoMPAS Private extension. * @param comment Some comments that will be added to the THistory entry being added. @@ -122,13 +127,13 @@ public String findByUUID(SclFileType type, UUID id, Version version) { * @return The ID of the new created SCL XML File in the database. */ @Transactional(REQUIRED) - public String create(SclFileType type, String name, String who, String comment, String sclData) { + public String create(String tenant, SclFileType type, String name, String who, String comment, String sclData) { var scl = converter.convertToElement(new BufferedInputStream(new ByteArrayInputStream(sclData.getBytes(StandardCharsets.UTF_8))), SCL_ELEMENT_NAME, SCL_NS_URI); if (scl == null) { throw new CompasException(NO_SCL_ELEMENT_FOUND_ERROR_CODE, "No valid SCL found in the passed SCL Data."); } - if (hasDuplicateSclName(type, name)) { + if (hasDuplicateSclName(tenant, type, name)) { throw new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, "Given name of SCL File already used."); } @@ -149,12 +154,12 @@ public String create(SclFileType type, String name, String who, String comment, var labels = validateLabels(scl); var newSclData = converter.convertToString(scl); - repository.create(type, id, name, newSclData, version, who, labels); + repository.create(tenant, type, id, name, newSclData, version, who, labels); return newSclData; } - public boolean hasDuplicateSclName(SclFileType type, String name){ - return repository.hasDuplicateSclName(type, name); + public boolean hasDuplicateSclName(String tenant, SclFileType type, String name){ + return repository.hasDuplicateSclName(tenant, type, name); } /** @@ -163,6 +168,7 @@ public boolean hasDuplicateSclName(SclFileType type, String name){ * the CoMPAS Private elements will also be copied, the SCL Name will only be copied if it isn't available in the * SCL XML File. * + * @param tenant The tenant to scope the data to. * @param type The type to update it for. * @param id The UUID of the record to update. * @param changeSetType The type of change to determine the new version. @@ -170,18 +176,18 @@ public boolean hasDuplicateSclName(SclFileType type, String name){ * @param sclData The SCL XML File with the updated content. */ @Transactional(REQUIRED) - public String update(SclFileType type, UUID id, ChangeSetType changeSetType, String who, String comment, String sclData) { + public String update(String tenant, SclFileType type, UUID id, ChangeSetType changeSetType, String who, String comment, String sclData) { var scl = converter.convertToElement(new BufferedInputStream(new ByteArrayInputStream(sclData.getBytes(StandardCharsets.UTF_8))), SCL_ELEMENT_NAME, SCL_NS_URI); if (scl == null) { throw new CompasException(NO_SCL_ELEMENT_FOUND_ERROR_CODE, "No valid SCL found in the passed SCL Data."); } - var currentSclMetaInfo = repository.findMetaInfoByUUID(type, id); + var currentSclMetaInfo = repository.findMetaInfoByUUID(tenant, type, id); var newFileName = getFilenameFromXML(scl); if (newFileName.isPresent() && !newFileName.get().equals(currentSclMetaInfo.getName()) - && repository.hasDuplicateSclName(type, newFileName.get())) { + && repository.hasDuplicateSclName(tenant, type, newFileName.get())) { throw new CompasException(DUPLICATE_SCL_NAME_ERROR_CODE, "Given name of SCL File already used."); } @@ -202,7 +208,7 @@ public String update(SclFileType type, UUID id, ChangeSetType changeSetType, Str var labels = validateLabels(scl); var newSclData = converter.convertToString(scl); - repository.create(type, id, newSclName, newSclData, version, who, labels); + repository.create(tenant, type, id, newSclName, newSclData, version, who, labels); return newSclData; } @@ -210,24 +216,26 @@ public String update(SclFileType type, UUID id, ChangeSetType changeSetType, Str /** * Delete all versions for a specific SCL File using it's ID. * - * @param type The type of SCL where to find the SCL File - * @param id The ID of the SCL File to delete. + * @param tenant The tenant to scope the data to. + * @param type The type of SCL where to find the SCL File + * @param id The ID of the SCL File to delete. */ @Transactional(REQUIRED) - public void delete(SclFileType type, UUID id) { - repository.delete(type, id); + public void delete(String tenant, SclFileType type, UUID id) { + repository.delete(tenant, type, id); } /** * Delete passed versions for a specific SCL File using it's ID. * + * @param tenant The tenant to scope the data to. * @param type The type of SCL where to find the SCL File * @param id The ID of the SCL File to delete. * @param version The version of that SCL File to delete. */ @Transactional(REQUIRED) - public void delete(SclFileType type, UUID id, Version version) { - repository.delete(type, id, version); + public void delete(String tenant, SclFileType type, UUID id, Version version) { + repository.delete(tenant, type, id, version); } /** diff --git a/service/src/test/java/org/lfenergy/compas/scl/data/service/CompasSclDataServiceTest.java b/service/src/test/java/org/lfenergy/compas/scl/data/service/CompasSclDataServiceTest.java index 142674b8..f97f8540 100644 --- a/service/src/test/java/org/lfenergy/compas/scl/data/service/CompasSclDataServiceTest.java +++ b/service/src/test/java/org/lfenergy/compas/scl/data/service/CompasSclDataServiceTest.java @@ -40,6 +40,7 @@ class CompasSclDataServiceTest { private static final Version INITIAL_VERSION = new Version("1.0.0"); private static final SclFileType SCL_TYPE = SclFileType.SCD; + private static final String TENANT = "test-tenant"; @Mock private CompasSclDataRepository compasSclDataRepository; @@ -56,12 +57,12 @@ void beforeEach() { @Test void list_WhenCalled_ThenRepositoryIsCalled() { - when(compasSclDataRepository.list(SCL_TYPE)).thenReturn(emptyList()); + when(compasSclDataRepository.list(TENANT, SCL_TYPE)).thenReturn(emptyList()); - var result = compasSclDataService.list(SCL_TYPE); + var result = compasSclDataService.list(TENANT, SCL_TYPE); assertNotNull(result); - verify(compasSclDataRepository).list(SCL_TYPE); + verify(compasSclDataRepository).list(TENANT, SCL_TYPE); } @Test @@ -72,49 +73,49 @@ void listVersionsByUUID_WhenCalledAndRepositoryReturnItemList_ThenListIsReturned var historyItem = mock(IHistoryItem.class); when(historyItem.getId()).thenReturn(id); List expectedResult = List.of(historyItem); - when(compasSclDataRepository.listVersionsByUUID(SCL_TYPE, uuid)).thenReturn(expectedResult); + when(compasSclDataRepository.listVersionsByUUID(TENANT, SCL_TYPE, uuid)).thenReturn(expectedResult); - var result = compasSclDataService.listVersionsByUUID(SCL_TYPE, uuid); + var result = compasSclDataService.listVersionsByUUID(TENANT, SCL_TYPE, uuid); assertNotNull(result); assertEquals(expectedResult.size(), result.size()); assertEquals(historyItem.getId(), result.get(0).getId()); - verify(compasSclDataRepository).listVersionsByUUID(SCL_TYPE, uuid); + verify(compasSclDataRepository).listVersionsByUUID(TENANT, SCL_TYPE, uuid); } @Test void listVersionsByUUID_WhenCalledAndRepositoryReturnsEmptyList_ThenExceptionIsThrown() { var uuid = UUID.randomUUID(); - when(compasSclDataRepository.listVersionsByUUID(SCL_TYPE, uuid)).thenReturn(emptyList()); + when(compasSclDataRepository.listVersionsByUUID(TENANT, SCL_TYPE, uuid)).thenReturn(emptyList()); var exception = assertThrows(CompasNoDataFoundException.class, () -> { - compasSclDataService.listVersionsByUUID(SCL_TYPE, uuid); + compasSclDataService.listVersionsByUUID(TENANT, SCL_TYPE, uuid); }); assertEquals(NO_DATA_FOUND_ERROR_CODE, exception.getErrorCode()); - verify(compasSclDataRepository).listVersionsByUUID(SCL_TYPE, uuid); + verify(compasSclDataRepository).listVersionsByUUID(TENANT, SCL_TYPE, uuid); } @Test void findByUUID_WhenCalledWithoutVersion_ThenRepositoryIsCalled() throws IOException { var uuid = UUID.randomUUID(); - when(compasSclDataRepository.findByUUID(SCL_TYPE, uuid)).thenReturn(readSCL("scl_test_file.scd")); + when(compasSclDataRepository.findByUUID(TENANT, SCL_TYPE, uuid)).thenReturn(readSCL("scl_test_file.scd")); - var result = compasSclDataService.findByUUID(SCL_TYPE, uuid); + var result = compasSclDataService.findByUUID(TENANT, SCL_TYPE, uuid); assertNotNull(result); - verify(compasSclDataRepository).findByUUID(SCL_TYPE, uuid); + verify(compasSclDataRepository).findByUUID(TENANT, SCL_TYPE, uuid); } @Test void findByUUID_WhenCalledWithVersion_ThenRepositoryIsCalled() throws IOException { var uuid = UUID.randomUUID(); var version = new Version(1, 0, 0); - when(compasSclDataRepository.findByUUID(SCL_TYPE, uuid, version)).thenReturn(readSCL("scl_test_file.scd")); + when(compasSclDataRepository.findByUUID(TENANT, SCL_TYPE, uuid, version)).thenReturn(readSCL("scl_test_file.scd")); - var result = compasSclDataService.findByUUID(SCL_TYPE, uuid, version); + var result = compasSclDataService.findByUUID(TENANT, SCL_TYPE, uuid, version); assertNotNull(result); - verify(compasSclDataRepository).findByUUID(SCL_TYPE, uuid, version); + verify(compasSclDataRepository).findByUUID(TENANT, SCL_TYPE, uuid, version); } @Test @@ -125,16 +126,16 @@ void create_WhenCalledWithOutCompasExtension_ThenSCLReturnedWithCorrectCompasExt var scl = readSCL("scl_test_file.scd"); - when(compasSclDataRepository.hasDuplicateSclName(SCL_TYPE, name)).thenReturn(false); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); + when(compasSclDataRepository.hasDuplicateSclName(TENANT, SCL_TYPE, name)).thenReturn(false); + doNothing().when(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); - scl = compasSclDataService.create(SCL_TYPE, name, who, comment, scl); + scl = compasSclDataService.create(TENANT, SCL_TYPE, name, who, comment, scl); assertNotNull(scl); assertCompasExtension(scl, name); assertHistoryItem(scl, 2, INITIAL_VERSION, comment); - verify(compasSclDataRepository).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); - verify(compasSclDataRepository).hasDuplicateSclName(SCL_TYPE, name); + verify(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); + verify(compasSclDataRepository).hasDuplicateSclName(TENANT, SCL_TYPE, name); } @Test @@ -146,16 +147,16 @@ void create_WhenCalledWithCompasExtension_ThenSCLReturnedWithCorrectCompasExtens var scl = readSCL("scl_test_file.scd"); scl = createCompasPrivate(scl, "JUSTANOTHERNAME"); - when(compasSclDataRepository.hasDuplicateSclName(SCL_TYPE, name)).thenReturn(false); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); + when(compasSclDataRepository.hasDuplicateSclName(TENANT, SCL_TYPE, name)).thenReturn(false); + doNothing().when(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); - scl = compasSclDataService.create(SCL_TYPE, name, who, comment, scl); + scl = compasSclDataService.create(TENANT, SCL_TYPE, name, who, comment, scl); assertNotNull(scl); assertCompasExtension(scl, name); assertHistoryItem(scl, 2, INITIAL_VERSION, comment); - verify(compasSclDataRepository).create(eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); - verify(compasSclDataRepository).hasDuplicateSclName(SCL_TYPE, name); + verify(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), any(UUID.class), eq(name), anyString(), eq(INITIAL_VERSION), eq(who), eq(emptyList())); + verify(compasSclDataRepository).hasDuplicateSclName(TENANT, SCL_TYPE, name); } @Test @@ -166,12 +167,12 @@ void create_WhenCalledWithDuplicateSclName_ThenCompasExceptionThrown() throws IO var scl = readSCL("scl_test_file.scd"); - when(compasSclDataRepository.hasDuplicateSclName(SCL_TYPE, name)).thenReturn(true); + when(compasSclDataRepository.hasDuplicateSclName(TENANT, SCL_TYPE, name)).thenReturn(true); var exception = assertThrows(CompasException.class, () -> { - compasSclDataService.create(SCL_TYPE, name, who, comment, scl); + compasSclDataService.create(TENANT, SCL_TYPE, name, who, comment, scl); }); assertEquals(DUPLICATE_SCL_NAME_ERROR_CODE, exception.getErrorCode()); - verify(compasSclDataRepository).hasDuplicateSclName(SCL_TYPE, name); + verify(compasSclDataRepository).hasDuplicateSclName(TENANT, SCL_TYPE, name); } @Test @@ -183,7 +184,7 @@ void create_WhenCalledWithXMLStringWithoutSCL_ThenCompasExceptionThrown() { var scl = ""; var exception = assertThrows(CompasException.class, () -> { - compasSclDataService.create(SCL_TYPE, name, who, comment, scl); + compasSclDataService.create(TENANT, SCL_TYPE, name, who, comment, scl); }); assertEquals(NO_SCL_ELEMENT_FOUND_ERROR_CODE, exception.getErrorCode()); } @@ -199,17 +200,17 @@ void update_WhenCalledWithoutCompasElements_ThenSCLReturnedWithCorrectCompasExte var scl = readSCL("scl_test_file.scd"); var sclMetaInfo = new SclMetaInfo(uuid.toString(), previousName, INITIAL_VERSION.toString()); - when(compasSclDataRepository.findMetaInfoByUUID(SCL_TYPE, uuid)).thenReturn(sclMetaInfo); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); + when(compasSclDataRepository.findMetaInfoByUUID(TENANT, SCL_TYPE, uuid)).thenReturn(sclMetaInfo); + doNothing().when(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); - scl = compasSclDataService.update(SCL_TYPE, uuid, changeSet, who, null, scl); + scl = compasSclDataService.update(TENANT, SCL_TYPE, uuid, changeSet, who, null, scl); assertNotNull(scl); assertCompasExtension(scl, previousName); assertHistoryItem(scl, 4, nextVersion, null); - verify(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); - verify(compasSclDataRepository).findMetaInfoByUUID(SCL_TYPE, uuid); - verify(compasSclDataRepository, never()).hasDuplicateSclName(SCL_TYPE, previousName); + verify(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); + verify(compasSclDataRepository).findMetaInfoByUUID(TENANT, SCL_TYPE, uuid); + verify(compasSclDataRepository, never()).hasDuplicateSclName(TENANT, SCL_TYPE, previousName); } @Test @@ -225,18 +226,18 @@ void update_WhenCalledWithCompasElementsAndNewName_ThenSCLReturnedWithCorrectCom scl = createCompasPrivate(scl, newName); var sclMetaInfo = new SclMetaInfo(uuid.toString(), previousName, INITIAL_VERSION.toString()); - when(compasSclDataRepository.findMetaInfoByUUID(SCL_TYPE, uuid)).thenReturn(sclMetaInfo); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(newName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); - when(compasSclDataRepository.hasDuplicateSclName(SCL_TYPE, newName)).thenReturn(false); + when(compasSclDataRepository.findMetaInfoByUUID(TENANT, SCL_TYPE, uuid)).thenReturn(sclMetaInfo); + doNothing().when(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), eq(uuid), eq(newName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); + when(compasSclDataRepository.hasDuplicateSclName(TENANT, SCL_TYPE, newName)).thenReturn(false); - scl = compasSclDataService.update(SCL_TYPE, uuid, changeSet, who, null, scl); + scl = compasSclDataService.update(TENANT, SCL_TYPE, uuid, changeSet, who, null, scl); assertNotNull(scl); assertCompasExtension(scl, newName); assertHistoryItem(scl, 4, nextVersion, null); - verify(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(newName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); - verify(compasSclDataRepository).findMetaInfoByUUID(SCL_TYPE, uuid); - verify(compasSclDataRepository).hasDuplicateSclName(SCL_TYPE, newName); + verify(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), eq(uuid), eq(newName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); + verify(compasSclDataRepository).findMetaInfoByUUID(TENANT, SCL_TYPE, uuid); + verify(compasSclDataRepository).hasDuplicateSclName(TENANT, SCL_TYPE, newName); } @Test @@ -250,15 +251,15 @@ void update_WhenCalledWithCompasElementsAndDuplicateNewName_ThenCompasExceptionT var scl = createCompasPrivate(readSCL("scl_test_file.scd"), newName); var sclMetaInfo = new SclMetaInfo(uuid.toString(), previousName, INITIAL_VERSION.toString()); - when(compasSclDataRepository.findMetaInfoByUUID(SCL_TYPE, uuid)).thenReturn(sclMetaInfo); - when(compasSclDataRepository.hasDuplicateSclName(SCL_TYPE, newName)).thenReturn(true); + when(compasSclDataRepository.findMetaInfoByUUID(TENANT, SCL_TYPE, uuid)).thenReturn(sclMetaInfo); + when(compasSclDataRepository.hasDuplicateSclName(TENANT, SCL_TYPE, newName)).thenReturn(true); var exception = assertThrows(CompasException.class, () -> { - compasSclDataService.update(SCL_TYPE, uuid, changeSet, who, null, scl); + compasSclDataService.update(TENANT, SCL_TYPE, uuid, changeSet, who, null, scl); }); assertEquals(DUPLICATE_SCL_NAME_ERROR_CODE, exception.getErrorCode()); - verify(compasSclDataRepository).findMetaInfoByUUID(SCL_TYPE, uuid); - verify(compasSclDataRepository).hasDuplicateSclName(SCL_TYPE, newName); + verify(compasSclDataRepository).findMetaInfoByUUID(TENANT, SCL_TYPE, uuid); + verify(compasSclDataRepository).hasDuplicateSclName(TENANT, SCL_TYPE, newName); } @Test @@ -273,17 +274,17 @@ void update_WhenCalledWithCompasElementsAndSameName_ThenSCLReturnedWithCorrectCo scl = createCompasPrivate(scl, previousName); var sclMetaInfo = new SclMetaInfo(uuid.toString(), previousName, INITIAL_VERSION.toString()); - when(compasSclDataRepository.findMetaInfoByUUID(SCL_TYPE, uuid)).thenReturn(sclMetaInfo); - doNothing().when(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); + when(compasSclDataRepository.findMetaInfoByUUID(TENANT, SCL_TYPE, uuid)).thenReturn(sclMetaInfo); + doNothing().when(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); - scl = compasSclDataService.update(SCL_TYPE, uuid, changeSet, who, null, scl); + scl = compasSclDataService.update(TENANT, SCL_TYPE, uuid, changeSet, who, null, scl); assertNotNull(scl); assertCompasExtension(scl, previousName); assertHistoryItem(scl, 4, nextVersion, null); - verify(compasSclDataRepository).create(eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); - verify(compasSclDataRepository).findMetaInfoByUUID(SCL_TYPE, uuid); - verify(compasSclDataRepository, never()).hasDuplicateSclName(SCL_TYPE, previousName); + verify(compasSclDataRepository).create(eq(TENANT), eq(SCL_TYPE), eq(uuid), eq(previousName), anyString(), eq(nextVersion), eq(who), eq(emptyList())); + verify(compasSclDataRepository).findMetaInfoByUUID(TENANT, SCL_TYPE, uuid); + verify(compasSclDataRepository, never()).hasDuplicateSclName(TENANT, SCL_TYPE, previousName); } @Test @@ -295,7 +296,7 @@ void update_WhenCalledWithXMLStringWithoutSCL_ThenCompasExceptionThrown() { var scl = ""; var exception = assertThrows(CompasException.class, () -> { - compasSclDataService.update(SCL_TYPE, uuid, changeSet, who, null, scl); + compasSclDataService.update(TENANT, SCL_TYPE, uuid, changeSet, who, null, scl); }); assertEquals(NO_SCL_ELEMENT_FOUND_ERROR_CODE, exception.getErrorCode()); } @@ -304,11 +305,11 @@ void update_WhenCalledWithXMLStringWithoutSCL_ThenCompasExceptionThrown() { void delete_WhenCalledWithoutVersion_ThenRepositoryIsCalled() { var uuid = UUID.randomUUID(); - doNothing().when(compasSclDataRepository).delete(SCL_TYPE, uuid); + doNothing().when(compasSclDataRepository).delete(TENANT, SCL_TYPE, uuid); - compasSclDataService.delete(SCL_TYPE, uuid); + compasSclDataService.delete(TENANT, SCL_TYPE, uuid); - verify(compasSclDataRepository).delete(SCL_TYPE, uuid); + verify(compasSclDataRepository).delete(TENANT, SCL_TYPE, uuid); } @Test @@ -316,11 +317,11 @@ void delete_WhenCalledWithVersion_ThenRepositoryIsCalled() { var uuid = UUID.randomUUID(); var version = new Version(1, 0, 0); - doNothing().when(compasSclDataRepository).delete(SCL_TYPE, uuid, version); + doNothing().when(compasSclDataRepository).delete(TENANT, SCL_TYPE, uuid, version); - compasSclDataService.delete(SCL_TYPE, uuid, version); + compasSclDataService.delete(TENANT, SCL_TYPE, uuid, version); - verify(compasSclDataRepository).delete(SCL_TYPE, uuid, version); + verify(compasSclDataRepository).delete(TENANT, SCL_TYPE, uuid, version); } @Test From f331cc2861e6004ef7510b887919b2bb1aa64af1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:31:46 +0000 Subject: [PATCH 3/3] fix: improve TenantService trailing slash and no-path URL handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Strip trailing slash before extracting last path segment so that "http://host/realms/compas/" correctly returns "compas" - Guard against bare hostname URLs (e.g. "http://host") returning the hostname as tenant by checking that the char before lastSlash is not also a slash (i.e. not the "://" separator) - Fix test method name typo: AssTenant → AsTenant - Update no-path test to assert GLOBAL_TENANT (was incorrectly asserting "host") - Add new trailing-slash test case --- .../compas/scl/data/rest/TenantService.java | 15 +++++++++++---- .../compas/scl/data/rest/TenantServiceTest.java | 13 +++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/lfenergy/compas/scl/data/rest/TenantService.java b/app/src/main/java/org/lfenergy/compas/scl/data/rest/TenantService.java index cb4e1c3b..7d915db3 100644 --- a/app/src/main/java/org/lfenergy/compas/scl/data/rest/TenantService.java +++ b/app/src/main/java/org/lfenergy/compas/scl/data/rest/TenantService.java @@ -43,10 +43,17 @@ public String resolveTenant() { return GLOBAL_TENANT; } // Issuer URL format (Keycloak): http:///auth/realms/ - // Extract the last path segment as the realm / tenant name. - int lastSlash = issuer.lastIndexOf('/'); - if (lastSlash >= 0 && lastSlash < issuer.length() - 1) { - return issuer.substring(lastSlash + 1); + // Strip any trailing slash so that "http://host/realms/compas/" is treated + // the same as "http://host/realms/compas". + String normalized = issuer.endsWith("/") ? issuer.substring(0, issuer.length() - 1) : issuer; + int lastSlash = normalized.lastIndexOf('/'); + // Guard against URLs with no path (e.g. "http://host") by ensuring the + // character before lastSlash is not also a slash (i.e. not the "://" part). + if (lastSlash > 0 && normalized.charAt(lastSlash - 1) != '/') { + String segment = normalized.substring(lastSlash + 1); + if (!segment.isBlank()) { + return segment; + } } } catch (Exception e) { // No token available – fall back to global tenant. diff --git a/app/src/test/java/org/lfenergy/compas/scl/data/rest/TenantServiceTest.java b/app/src/test/java/org/lfenergy/compas/scl/data/rest/TenantServiceTest.java index d71f14e9..fcabc6c4 100644 --- a/app/src/test/java/org/lfenergy/compas/scl/data/rest/TenantServiceTest.java +++ b/app/src/test/java/org/lfenergy/compas/scl/data/rest/TenantServiceTest.java @@ -38,7 +38,7 @@ void resolveTenant_WhenIssuerIsBlank_ThenReturnsGlobalTenant() { } @Test - void resolveTenant_WhenIssuerHasRealmPath_ThenReturnsRealmAssTenant() { + void resolveTenant_WhenIssuerHasRealmPath_ThenReturnsRealmAsTenant() { when(jsonWebToken.getIssuer()).thenReturn("http://host/auth/realms/compas"); assertEquals("compas", tenantService.resolveTenant()); @@ -55,9 +55,14 @@ void resolveTenant_WhenIssuerHasDifferentRealm_ThenReturnsCorrectTenant() { void resolveTenant_WhenIssuerHasNoPath_ThenReturnsGlobalTenant() { when(jsonWebToken.getIssuer()).thenReturn("http://host"); - // "http://host" has a last slash at position 6 (before "host"), - // so host is extracted as tenant - assertEquals("host", tenantService.resolveTenant()); + assertEquals(GLOBAL_TENANT, tenantService.resolveTenant()); + } + + @Test + void resolveTenant_WhenIssuerHasTrailingSlash_ThenReturnsRealmAsTenant() { + when(jsonWebToken.getIssuer()).thenReturn("http://host/realms/compas/"); + + assertEquals("compas", tenantService.resolveTenant()); } @Test