From 2f0eb9c1ecd609b69bfdda2a26f8142fd01c20fb Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Sun, 14 Dec 2025 19:03:01 +0530 Subject: [PATCH 01/13] [INJICERT-1239] feat(config): add AuthorizationServerConfig and AuthorizationServerMetadata classes Signed-off-by: amaydixit11 --- .../certify/core/constants/Constants.java | 3 ++ .../core/constants/ErrorConstants.java | 4 ++ .../core/dto/AuthorizationServerConfig.java | 26 ++++++++++ .../core/dto/AuthorizationServerMetadata.java | 50 +++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 certify-core/src/main/java/io/mosip/certify/core/dto/AuthorizationServerConfig.java create mode 100644 certify-core/src/main/java/io/mosip/certify/core/dto/AuthorizationServerMetadata.java diff --git a/certify-core/src/main/java/io/mosip/certify/core/constants/Constants.java b/certify-core/src/main/java/io/mosip/certify/core/constants/Constants.java index fd4ce3c3..51fd6d2a 100644 --- a/certify-core/src/main/java/io/mosip/certify/core/constants/Constants.java +++ b/certify-core/src/main/java/io/mosip/certify/core/constants/Constants.java @@ -45,4 +45,7 @@ public class Constants { public static final String PRE_AUTH_CODE_PREFIX = "pre_auth_code:"; public static final String CREDENTIAL_OFFER_PREFIX = "credential_offer:"; public static final String PRE_AUTHORIZED_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:pre-authorized_code"; + public static final String AS_METADATA_PREFIX = "as_metadata:"; + public static final String WELL_KNOWN_OAUTH_AS = "/.well-known/oauth-authorization-server"; + public static final String WELL_KNOWN_OIDC_CONFIG = "/.well-known/openid-configuration"; } diff --git a/certify-core/src/main/java/io/mosip/certify/core/constants/ErrorConstants.java b/certify-core/src/main/java/io/mosip/certify/core/constants/ErrorConstants.java index 4c57aeaa..842e317d 100644 --- a/certify-core/src/main/java/io/mosip/certify/core/constants/ErrorConstants.java +++ b/certify-core/src/main/java/io/mosip/certify/core/constants/ErrorConstants.java @@ -57,4 +57,8 @@ public class ErrorConstants { public static final String UNKNOWN_CLAIMS = "unknown_claims"; public static final String INVALID_EXPIRY_RANGE = "invalid_expiry_range"; public static final String INVALID_OFFER_ID_FORMAT = "invalid_offer_id_format"; + public static final String AUTHORIZATION_SERVER_DISCOVERY_FAILED = "authorization_server_discovery_failed"; + public static final String INVALID_AUTHORIZATION_SERVER = "invalid_authorization_server"; + public static final String AUTHORIZATION_SERVER_NOT_CONFIGURED = "authorization_server_not_configured"; + } diff --git a/certify-core/src/main/java/io/mosip/certify/core/dto/AuthorizationServerConfig.java b/certify-core/src/main/java/io/mosip/certify/core/dto/AuthorizationServerConfig.java new file mode 100644 index 00000000..a0135934 --- /dev/null +++ b/certify-core/src/main/java/io/mosip/certify/core/dto/AuthorizationServerConfig.java @@ -0,0 +1,26 @@ +package io.mosip.certify.core.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * Configuration for a single authorization server + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class AuthorizationServerConfig implements Serializable { + private static final long serialVersionUID = 1L; + + private String serverId; + private String serverUrl; + private boolean internal; + private String wellKnownUrl; + private long metadataCachedAt; + private AuthorizationServerMetadata metadata; +} \ No newline at end of file diff --git a/certify-core/src/main/java/io/mosip/certify/core/dto/AuthorizationServerMetadata.java b/certify-core/src/main/java/io/mosip/certify/core/dto/AuthorizationServerMetadata.java new file mode 100644 index 00000000..a092bb37 --- /dev/null +++ b/certify-core/src/main/java/io/mosip/certify/core/dto/AuthorizationServerMetadata.java @@ -0,0 +1,50 @@ +package io.mosip.certify.core.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * Authorization Server Metadata as per RFC 8414 + * Source: https://www.rfc-editor.org/rfc/rfc8414.html + * Used for discovery via /.well-known/oauth-authorization-server + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class AuthorizationServerMetadata { + + @JsonProperty("issuer") + private String issuer; + + @JsonProperty("token_endpoint") + private String tokenEndpoint; + + @JsonProperty("jwks_uri") + private String jwksUri; + + @JsonProperty("authorization_endpoint") + private String authorizationEndpoint; + + @JsonProperty("response_types_supported") + private List responseTypesSupported; + + @JsonProperty("grant_types_supported") + private List grantTypesSupported; + + @JsonProperty("token_endpoint_auth_methods_supported") + private List tokenEndpointAuthMethodsSupported; + + @JsonProperty("code_challenge_methods_supported") + private List codeChallengeMethodsSupported; + + @JsonProperty("scopes_supported") + private List scopesSupported; +} \ No newline at end of file From bb44e31adc94f682720c315fda1d7c7ca1a34c30 Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Sun, 14 Dec 2025 19:07:02 +0530 Subject: [PATCH 02/13] [INJICERT-1239] feat(authorization): implement AuthorizationServerService for managing and discovering authorization server metadata Signed-off-by: amaydixit11 --- .../services/AuthorizationServerService.java | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 certify-service/src/main/java/io/mosip/certify/services/AuthorizationServerService.java diff --git a/certify-service/src/main/java/io/mosip/certify/services/AuthorizationServerService.java b/certify-service/src/main/java/io/mosip/certify/services/AuthorizationServerService.java new file mode 100644 index 00000000..4374b7e6 --- /dev/null +++ b/certify-service/src/main/java/io/mosip/certify/services/AuthorizationServerService.java @@ -0,0 +1,327 @@ +package io.mosip.certify.services; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.certify.core.constants.Constants; +import io.mosip.certify.core.constants.ErrorConstants; +import io.mosip.certify.core.dto.AuthorizationServerConfig; +import io.mosip.certify.core.dto.AuthorizationServerMetadata; +import io.mosip.certify.core.exception.CertifyException; +import io.mosip.certify.core.exception.InvalidRequestException; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestTemplate; +import io.mosip.certify.services.VCICacheService; + +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Service for discovering and caching authorization server metadata + */ +@Service +@Slf4j +public class AuthorizationServerService { + + @Autowired + private VCICacheService vciCacheService; + + @Autowired + private ObjectMapper objectMapper; + + @Autowired + private RestTemplate restTemplate; + + @Value("${mosip.certify.authorization.discovery.retry-count:3}") + private int retryCount; + + @Value("${mosip.certify.authorization.servers:}") + private String authorizationServersConfig; + + @Value("${mosip.certify.authorization.internal.url:}") + private String internalAuthServerUrl; + + @Value("${mosip.certify.authorization.default-server:}") + private String defaultAuthServer; + + @Value("${mosip.certify.credential-config.as-mapping:{}}") + private String credentialConfigMappingJson; + + private List configuredServers; + private Map credentialConfigToASMapping; + + @PostConstruct + public void initialize() { + log.info("Initializing Authorization Server Management Service"); + + configuredServers = new ArrayList<>(); + loadConfiguredServers(); + loadCredentialConfigMappings(); + + log.info("Configured {} authorization servers", configuredServers.size()); + log.info("Loaded {} credential configuration mappings", credentialConfigToASMapping.size()); + } + + private void loadConfiguredServers() { + // Add internal auth server + if (StringUtils.hasText(internalAuthServerUrl)) { + AuthorizationServerConfig internal = AuthorizationServerConfig.builder() + .serverId("internal") + .serverUrl(internalAuthServerUrl) + .internal(true) + .build(); + configuredServers.add(internal); + log.info("Added internal authorization server: {}", internalAuthServerUrl); + } + + // Parse external auth servers + if (StringUtils.hasText(authorizationServersConfig)) { + String[] servers = authorizationServersConfig.split(","); + for (String serverUrl : servers) { + serverUrl = serverUrl.trim(); + if (StringUtils.hasText(serverUrl)) { + AuthorizationServerConfig config = AuthorizationServerConfig.builder() + .serverId(generateServerId(serverUrl)) + .serverUrl(serverUrl) + .internal(false) + .build(); + configuredServers.add(config); + log.info("Added external authorization server: {}", serverUrl); + } + } + } + + if (configuredServers.isEmpty()) { + log.warn("No authorization servers configured"); + } + } + + private void loadCredentialConfigMappings() { + credentialConfigToASMapping = new HashMap<>(); + + try { + if (StringUtils.hasText(credentialConfigMappingJson) && + !credentialConfigMappingJson.trim().equals("{}")) { + + Map mappings = objectMapper.readValue( + credentialConfigMappingJson, + new TypeReference>() { + }); + + credentialConfigToASMapping.putAll(mappings); + log.info("Loaded credential config mappings: {}", mappings); + } + } catch (Exception e) { + log.error("Failed to parse credential config mappings", e); + } + } + + /** + * Get internal authorization server metadata + */ + public AuthorizationServerMetadata getInternalAuthServerMetadata() { + // For internal server, we can construct metadata directly + AuthorizationServerMetadata metadata = new AuthorizationServerMetadata(); + metadata.setIssuer(normalizeUrl(internalAuthServerUrl)); + metadata.setTokenEndpoint(normalizeUrl(internalAuthServerUrl) + "/token"); + metadata.setAuthorizationEndpoint(normalizeUrl(internalAuthServerUrl) + "/authorize"); + metadata.setJwksUri(normalizeUrl(internalAuthServerUrl) + "/jwks.json"); + + return metadata; + } + + /** + * Discover authorization server metadata from well-known endpoint + */ + public AuthorizationServerMetadata discoverMetadata(String serverUrl) { + log.info("Discovering authorization server metadata for: {}", serverUrl); + + // Check cache first + AuthorizationServerMetadata cached = vciCacheService.getASMetadata(serverUrl); + if (cached != null) { + log.info("Using cached AS metadata for: {}", serverUrl); + return cached; + } + + // Try OIDC config first (per RFC 8414 compatibility notes), then OAuth AS + // discovery + AuthorizationServerMetadata metadata = tryDiscoveryEndpoint(serverUrl, Constants.WELL_KNOWN_OIDC_CONFIG); + if (metadata == null) { + log.info("OIDC configuration discovery failed, trying OAuth AS endpoint"); + metadata = tryDiscoveryEndpoint(serverUrl, Constants.WELL_KNOWN_OAUTH_AS); + } + + if (metadata == null) { + log.error("Failed to discover AS metadata for: {}", serverUrl); + throw new CertifyException(ErrorConstants.AUTHORIZATION_SERVER_DISCOVERY_FAILED, + "Could not discover authorization server metadata"); + } + + // Cache the metadata + vciCacheService.setASMetadata(serverUrl, metadata); + log.info("Successfully discovered and cached AS metadata for: {}", serverUrl); + + return metadata; + } + + private AuthorizationServerMetadata tryDiscoveryEndpoint(String serverUrl, String wellKnownPath) { + String discoveryUrl = normalizeUrl(serverUrl) + wellKnownPath; + + for (int attempt = 1; attempt <= retryCount; attempt++) { + try { + log.debug("Discovery attempt {} for URL: {}", attempt, discoveryUrl); + + ResponseEntity response = restTemplate.getForEntity(new URI(discoveryUrl), String.class); + + if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { + AuthorizationServerMetadata metadata = objectMapper.readValue( + response.getBody(), + AuthorizationServerMetadata.class); + + validateMetadata(metadata, serverUrl); + return metadata; + } + } catch (Exception e) { + log.warn("Discovery attempt {} failed for {}: {}", attempt, discoveryUrl, e.getMessage()); + if (attempt == retryCount) { + log.error("All discovery attempts failed for: {}", discoveryUrl, e); + } + } + } + return null; + } + + private void validateMetadata(AuthorizationServerMetadata metadata, String expectedIssuer) { + if (metadata == null + || !StringUtils.hasText(metadata.getIssuer()) + || !StringUtils.hasText(metadata.getTokenEndpoint())) { + throw new CertifyException(ErrorConstants.AUTHORIZATION_SERVER_DISCOVERY_FAILED); + } + + // Validate issuer matches expected URL + String normalizedExpected = normalizeUrl(expectedIssuer); + String normalizedActual = normalizeUrl(metadata.getIssuer()); + + if (!normalizedActual.equals(normalizedExpected)) { + log.warn("Issuer mismatch: expected {}, got {}", normalizedExpected, normalizedActual); + } + } + + /** + * Get token endpoint for a specific authorization server + */ + public String getTokenEndpoint(String serverUrl) { + AuthorizationServerMetadata metadata = discoverMetadata(serverUrl); + return metadata.getTokenEndpoint(); + } + + /** + * Get JWKS URI for a specific authorization server + */ + public String getJwksUri(String serverUrl) { + AuthorizationServerMetadata metadata = discoverMetadata(serverUrl); + return metadata.getJwksUri(); + } + + /** + * Check if authorization server supports pre-authorized code grant + */ + public boolean supportsPreAuthorizedCodeGrant(String serverUrl) { + try { + AuthorizationServerMetadata metadata = discoverMetadata(serverUrl); + List grantTypes = metadata.getGrantTypesSupported(); + return grantTypes != null && grantTypes.contains(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + } catch (Exception e) { + log.warn("Could not check grant type support for {}: {}", serverUrl, e.getMessage()); + return false; + } + } + + /** + * Get authorization server for a specific credential configuration + */ + public String getAuthorizationServerForCredentialConfig(String credentialConfigId) { + log.debug("Getting authorization server for credential config: {}", credentialConfigId); + + // Check if there's a specific mapping + String mappedServerUrl = credentialConfigToASMapping.get(credentialConfigId); + if (StringUtils.hasText(mappedServerUrl)) { + validateServerConfigured(mappedServerUrl); + log.debug("Found mapped AS for {}: {}", credentialConfigId, mappedServerUrl); + return mappedServerUrl; + } + + // Use default server if configured + if (StringUtils.hasText(defaultAuthServer)) { + validateServerConfigured(defaultAuthServer); + log.debug("Using default AS for {}: {}", credentialConfigId, defaultAuthServer); + return defaultAuthServer; + } + + // Fall back to internal server + if (StringUtils.hasText(internalAuthServerUrl)) { + log.debug("Using internal AS for {}: {}", credentialConfigId, internalAuthServerUrl); + return internalAuthServerUrl; + } + + log.error("No authorization server found for credential config: {}", credentialConfigId); + throw new CertifyException(ErrorConstants.AUTHORIZATION_SERVER_NOT_CONFIGURED, + "No authorization server configured for credential configuration: " + credentialConfigId); + } + + /** + * Get all configured authorization server URLs + */ + public List getAllAuthorizationServerUrls() { + return configuredServers.stream() + .map(AuthorizationServerConfig::getServerUrl) + .collect(Collectors.toList()); + } + + /** + * Check if a server URL is configured + */ + public boolean isServerConfigured(String serverUrl) { + String normalized = normalizeUrl(serverUrl); + return configuredServers.stream() + .anyMatch(config -> normalizeUrl(config.getServerUrl()).equals(normalized)); + } + + private void validateServerConfigured(String serverUrl) { + if (!isServerConfigured(serverUrl)) { + log.error("Authorization server not configured: {}", serverUrl); + throw new InvalidRequestException(ErrorConstants.INVALID_AUTHORIZATION_SERVER); + } + } + + /** + * Normalize URL by removing trailing slashes + */ + private String normalizeUrl(String url) { + if (url == null) { + return ""; + } + return url.replaceAll("/+$", ""); + } + + /** + * Generate unique server ID from URL + */ + private String generateServerId(String serverUrl) { + try { + String normalized = normalizeUrl(serverUrl); + String domain = normalized.replaceAll("https?://", "") + .replaceAll("[^a-zA-Z0-9-]", "-"); + return "as-" + domain; + } catch (Exception e) { + return "as-" + UUID.randomUUID().toString(); + } + } +} \ No newline at end of file From 0d1bb8d5bc2b34cbd6b75102ed9b0228c848a0c2 Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Sun, 14 Dec 2025 19:07:36 +0530 Subject: [PATCH 03/13] [INJICERT-1239] feat(CredentialOfferResponse): add authorization_server property to response Signed-off-by: amaydixit11 --- .../io/mosip/certify/core/dto/CredentialOfferResponse.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/certify-core/src/main/java/io/mosip/certify/core/dto/CredentialOfferResponse.java b/certify-core/src/main/java/io/mosip/certify/core/dto/CredentialOfferResponse.java index 4386371c..d96b33d7 100644 --- a/certify-core/src/main/java/io/mosip/certify/core/dto/CredentialOfferResponse.java +++ b/certify-core/src/main/java/io/mosip/certify/core/dto/CredentialOfferResponse.java @@ -22,4 +22,7 @@ public class CredentialOfferResponse { @JsonProperty("grants") private Grant grants; + + @JsonProperty("authorization_server") + private String authorizationServer; } \ No newline at end of file From d92363734a4222c562c141a357aa554de41a8eaf Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Sun, 14 Dec 2025 19:11:44 +0530 Subject: [PATCH 04/13] [INJICERT-1239] feat(CredentialConfiguration): enhance authorization server handling and metadata retrieval Signed-off-by: amaydixit11 --- .../java/io/mosip/certify/core/dto/Grant.java | 20 +++- .../mosip/certify/core/dto/TokenRequest.java | 42 ++++---- .../PreAuthorizedCodeController.java | 7 +- .../controller/WellKnownController.java | 21 +++- .../CredentialConfigurationServiceImpl.java | 13 ++- .../services/PreAuthorizedCodeService.java | 36 ++++--- .../certify/services/VCICacheService.java | 99 ++++++++++--------- 7 files changed, 141 insertions(+), 97 deletions(-) diff --git a/certify-core/src/main/java/io/mosip/certify/core/dto/Grant.java b/certify-core/src/main/java/io/mosip/certify/core/dto/Grant.java index dae8f7f9..4b39ffdf 100644 --- a/certify-core/src/main/java/io/mosip/certify/core/dto/Grant.java +++ b/certify-core/src/main/java/io/mosip/certify/core/dto/Grant.java @@ -13,13 +13,16 @@ public class Grant { @JsonProperty("urn:ietf:params:oauth:grant-type:pre-authorized_code") - private PreAuthorizedCodeGrant preAuthorizedCode; + private PreAuthorizedCodeGrantType preAuthorizedCode; + + @JsonProperty("authorization_code") + private AuthorizedCodeGrantType authorizationCode; @Data @Builder @AllArgsConstructor @NoArgsConstructor - public static class PreAuthorizedCodeGrant { + public static class PreAuthorizedCodeGrantType { @JsonProperty("pre-authorized_code") private String preAuthorizedCode; @@ -27,4 +30,17 @@ public static class PreAuthorizedCodeGrant { @JsonProperty("tx_code") private TxCode txCode; } + + @Data + @Builder + @AllArgsConstructor + @NoArgsConstructor + public static class AuthorizedCodeGrantType { + + @JsonProperty("issuer_state") + private String issuerState; + + @JsonProperty("authorization_server") + private String authorizationServer; + } } \ No newline at end of file diff --git a/certify-core/src/main/java/io/mosip/certify/core/dto/TokenRequest.java b/certify-core/src/main/java/io/mosip/certify/core/dto/TokenRequest.java index 3d452667..a0058bce 100644 --- a/certify-core/src/main/java/io/mosip/certify/core/dto/TokenRequest.java +++ b/certify-core/src/main/java/io/mosip/certify/core/dto/TokenRequest.java @@ -10,30 +10,32 @@ @NoArgsConstructor @AllArgsConstructor public class TokenRequest { -// @NotBlank(message = "Grant type is required") + @JsonProperty("grant_type") private String grantType; + @JsonProperty("pre-authorized_code") private String preAuthorizedCode; + @JsonProperty("tx_code") private String txCode; -// // Optional: client_id (only when client authentication requires it) -// @JsonProperty("client_id") -// private String clientId; -// -// // Optional: code_verifier (for PKCE in hybrid flows, if used) -// @JsonProperty("code_verifier") -// private String codeVerifier; -// -// // Optional: redirect_uri (only in authorization_code flow) -// @JsonProperty("redirect_uri") -// private String redirectUri; -// -// // Optional: authorization_details (per RFC9396, for openid_credential) -// @JsonProperty("authorization_details") -// private String authorizationDetails; -// -// // Optional: resource (per RFC8707, recommended when multiple issuers) -// @JsonProperty("resource") -// private String resource; + // // Optional: client_id (only when client authentication requires it) + // @JsonProperty("client_id") + // private String clientId; + // + // // Optional: code_verifier (for PKCE in hybrid flows, if used) + // @JsonProperty("code_verifier") + // private String codeVerifier; + // + // // Optional: redirect_uri (only in authorization_code flow) + // @JsonProperty("redirect_uri") + // private String redirectUri; + // + // // Optional: authorization_details (per RFC9396, for openid_credential) + // @JsonProperty("authorization_details") + // private String authorizationDetails; + // + // // Optional: resource (per RFC8707, recommended when multiple issuers) + // @JsonProperty("resource") + // private String resource; } \ No newline at end of file diff --git a/certify-service/src/main/java/io/mosip/certify/controller/PreAuthorizedCodeController.java b/certify-service/src/main/java/io/mosip/certify/controller/PreAuthorizedCodeController.java index f074d15a..ec4d3de2 100644 --- a/certify-service/src/main/java/io/mosip/certify/controller/PreAuthorizedCodeController.java +++ b/certify-service/src/main/java/io/mosip/certify/controller/PreAuthorizedCodeController.java @@ -31,11 +31,8 @@ public CredentialOfferResponse getCredentialOffer(@PathVariable("offer_id") Stri return preAuthorizedCodeService.getCredentialOffer(offerId); } - @PostMapping(value = "/token", -// consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE - ) - public TokenResponse token(@RequestBody TokenRequest request) { + @PostMapping(value = "/token", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public TokenResponse token(@ModelAttribute TokenRequest request) { return preAuthorizedCodeService.exchangePreAuthorizedCode(request); } } diff --git a/certify-service/src/main/java/io/mosip/certify/controller/WellKnownController.java b/certify-service/src/main/java/io/mosip/certify/controller/WellKnownController.java index 756a64b4..40312b16 100644 --- a/certify-service/src/main/java/io/mosip/certify/controller/WellKnownController.java +++ b/certify-service/src/main/java/io/mosip/certify/controller/WellKnownController.java @@ -1,13 +1,12 @@ package io.mosip.certify.controller; +import io.mosip.certify.core.dto.AuthorizationServerMetadata; import io.mosip.certify.core.dto.CredentialIssuerMetadataDTO; import io.mosip.certify.core.spi.CredentialConfigurationService; import io.mosip.certify.core.spi.VCIssuanceService; +import io.mosip.certify.services.AuthorizationServerService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.Map; @@ -21,15 +20,27 @@ public class WellKnownController { @Autowired private VCIssuanceService vcIssuanceService; + @Autowired + private AuthorizationServerService authorizationServerService; + @GetMapping(value = "/openid-credential-issuer", produces = "application/json") public CredentialIssuerMetadataDTO getCredentialIssuerMetadata( @RequestParam(name = "version", required = false, defaultValue = "latest") String version) { return credentialConfigurationService.fetchCredentialIssuerMetadata(version); } + @GetMapping(value = "/oauth-authorization-server", produces = "application/json") + public AuthorizationServerMetadata getAuthorizationServerMetadata() { + return authorizationServerService.getInternalAuthServerMetadata(); + } + + @GetMapping(value = "/openid-configuration", produces = "application/json") + public AuthorizationServerMetadata getOpenIDConfiguration() { + return authorizationServerService.getInternalAuthServerMetadata(); + } + @GetMapping(value = "/did.json") public Map getDIDDocument() { return vcIssuanceService.getDIDDocument(); } } - diff --git a/certify-service/src/main/java/io/mosip/certify/services/CredentialConfigurationServiceImpl.java b/certify-service/src/main/java/io/mosip/certify/services/CredentialConfigurationServiceImpl.java index be2cc202..01ea3e86 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/CredentialConfigurationServiceImpl.java +++ b/certify-service/src/main/java/io/mosip/certify/services/CredentialConfigurationServiceImpl.java @@ -39,6 +39,9 @@ public class CredentialConfigurationServiceImpl implements CredentialConfigurati @Autowired private CredentialConfigMapper credentialConfigMapper; + @Autowired + private AuthorizationServerService authServerService; + @Value("${mosip.certify.domain.url:}") private String credentialIssuer; @@ -269,7 +272,8 @@ public CredentialIssuerMetadataDTO fetchCredentialIssuerMetadata(String version) }); credentialIssuerMetadata.setCredentialConfigurationSupportedDTO(credentialConfigurationSupportedMap); credentialIssuerMetadata.setCredentialIssuer(credentialIssuer); - credentialIssuerMetadata.setAuthorizationServers(Collections.singletonList(authUrl)); + List authServers = authServerService.getAllAuthorizationServerUrls(); + credentialIssuerMetadata.setAuthorizationServers(authServers); String credentialEndpoint = credentialIssuer + servletPath + "/issuance" + (!version.equals("latest") ? "/" + version : "") + "/credential"; credentialIssuerMetadata.setCredentialEndpoint(credentialEndpoint); credentialIssuerMetadata.setDisplay(issuerDisplay); @@ -287,7 +291,9 @@ public CredentialIssuerMetadataDTO fetchCredentialIssuerMetadata(String version) }); credentialIssuerMetadata.setCredentialConfigurationSupportedDTO(credentialConfigurationSupportedMap); // Use a different setter for vd12 credentialIssuerMetadata.setCredentialIssuer(credentialIssuer); - credentialIssuerMetadata.setAuthorizationServers(Collections.singletonList(authUrl)); + // credentialIssuerMetadata.setAuthorizationServers(Collections.singletonList(authUrl)); + List authServers = authServerService.getAllAuthorizationServerUrls(); + credentialIssuerMetadata.setAuthorizationServers(authServers); String credentialEndpoint = credentialIssuer + servletPath + "/issuance/" + version + "/credential"; credentialIssuerMetadata.setCredentialEndpoint(credentialEndpoint); credentialIssuerMetadata.setDisplay(issuerDisplay); @@ -306,7 +312,8 @@ public CredentialIssuerMetadataDTO fetchCredentialIssuerMetadata(String version) }); credentialIssuerMetadata.setCredentialConfigurationSupportedDTO(credentialConfigurationSupportedList); // Use a different setter for vd11 credentialIssuerMetadata.setCredentialIssuer(credentialIssuer); - credentialIssuerMetadata.setAuthorizationServers(Collections.singletonList(authUrl)); + List authServers = authServerService.getAllAuthorizationServerUrls(); + credentialIssuerMetadata.setAuthorizationServers(authServers); String credentialEndpoint = credentialIssuer + servletPath + "/issuance/" + version + "/credential"; credentialIssuerMetadata.setCredentialEndpoint(credentialEndpoint); credentialIssuerMetadata.setDisplay(issuerDisplay); diff --git a/certify-service/src/main/java/io/mosip/certify/services/PreAuthorizedCodeService.java b/certify-service/src/main/java/io/mosip/certify/services/PreAuthorizedCodeService.java index f969d177..25f2d5e3 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/PreAuthorizedCodeService.java +++ b/certify-service/src/main/java/io/mosip/certify/services/PreAuthorizedCodeService.java @@ -5,6 +5,7 @@ import io.mosip.certify.core.dto.*; import io.mosip.certify.core.exception.CertifyException; import io.mosip.certify.core.exception.InvalidRequestException; +import io.mosip.certify.core.spi.CredentialConfigurationService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -23,6 +24,12 @@ public class PreAuthorizedCodeService { @Autowired private VCICacheService vciCacheService; + @Autowired + private CredentialConfigurationService credentialConfigurationService; + + @Autowired + private AuthorizationServerService authServerService; + @Value("${mosip.certify.identifier}") private String issuerIdentifier; @@ -51,12 +58,8 @@ public class PreAuthorizedCodeService { private static final String ALPHANUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; public String generatePreAuthorizedCode(PreAuthorizedRequest request) { - log.info("Generating pre-authorized code for credential configuration: {}", request.getCredentialConfigurationId()); - validatePreAuthorizedRequest(request); - int expirySeconds = request.getExpiresIn() != null ? request.getExpiresIn() : defaultExpirySeconds; - if (expirySeconds < minExpirySeconds || expirySeconds > maxExpirySeconds) { log.error("expires_in {} out of bounds [{}, {}]", expirySeconds, minExpirySeconds, maxExpirySeconds); throw new InvalidRequestException(ErrorConstants.INVALID_EXPIRY_RANGE); @@ -78,24 +81,24 @@ public String generatePreAuthorizedCode(PreAuthorizedRequest request) { CredentialOfferResponse offerResponse = buildCredentialOffer(request.getCredentialConfigurationId(), preAuthCode, request.getTxCode()); vciCacheService.setCredentialOffer(offerId, offerResponse); - String offerUri = buildCredentialOfferUri(offerId); - log.info("Successfully generated pre-authorized code with offer ID: {}", offerId); - - return offerUri; + return buildCredentialOfferUri(offerId); } private void validatePreAuthorizedRequest(PreAuthorizedRequest request) { - Map metadata = vciCacheService.getIssuerMetadata(); - Map supportedConfigs = (Map) metadata - .get(Constants.CREDENTIAL_CONFIGURATIONS_SUPPORTED); + // Map metadata = vciCacheService.getIssuerMetadata(); + CredentialIssuerMetadataDTO metadata = credentialConfigurationService.fetchCredentialIssuerMetadata("latest"); + // Map supportedConfigs = (Map) + // metadata.get(Constants.CREDENTIAL_CONFIGURATIONS_SUPPORTED); + Map supportedConfigs = metadata + .getCredentialConfigurationSupportedDTO(); if (supportedConfigs == null || !supportedConfigs.containsKey(request.getCredentialConfigurationId())) { log.error("Invalid credential configuration ID: {}", request.getCredentialConfigurationId()); throw new InvalidRequestException(ErrorConstants.INVALID_CREDENTIAL_CONFIGURATION_ID); } - Map config = (Map) supportedConfigs.get(request.getCredentialConfigurationId()); - Map requiredClaims = (Map) config.get(Constants.CLAIMS); + CredentialConfigurationSupportedDTO config = supportedConfigs.get(request.getCredentialConfigurationId()); + Map requiredClaims = config.getClaims(); validateClaims(requiredClaims, request.getClaims()); } @@ -192,16 +195,19 @@ private String generateUniquePreAuthCode() { } private CredentialOfferResponse buildCredentialOffer(String configId, String preAuthCode, String txnCode) { - Grant.PreAuthorizedCodeGrant grant = Grant.PreAuthorizedCodeGrant.builder() + Grant.PreAuthorizedCodeGrantType grant = Grant.PreAuthorizedCodeGrantType.builder() .preAuthorizedCode(preAuthCode) .txCode(StringUtils.hasText(txnCode) ? buildTxCodeInfo(txnCode) : null).build(); + String authorizationServer = authServerService.getAuthorizationServerForCredentialConfig(configId); Grant grants = Grant.builder().preAuthorizedCode(grant).build(); return CredentialOfferResponse.builder() .credentialIssuer(issuerIdentifier) .credentialConfigurationIds(Collections.singletonList(configId)) - .grants(grants).build(); + .grants(grants) + .authorizationServer(authorizationServer) + .build(); } private TxCode buildTxCodeInfo(String txnCode) { diff --git a/certify-service/src/main/java/io/mosip/certify/services/VCICacheService.java b/certify-service/src/main/java/io/mosip/certify/services/VCICacheService.java index 67a12303..8744656a 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/VCICacheService.java +++ b/certify-service/src/main/java/io/mosip/certify/services/VCICacheService.java @@ -2,10 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.mosip.certify.core.constants.Constants; -import io.mosip.certify.core.dto.CredentialOfferResponse; -import io.mosip.certify.core.dto.PreAuthCodeData; -import io.mosip.certify.core.dto.Transaction; -import io.mosip.certify.core.dto.VCIssuanceTransaction; +import io.mosip.certify.core.dto.*; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -19,6 +16,7 @@ import java.time.Duration; import java.util.HashMap; +import java.util.List; import java.util.Map; @Slf4j @@ -28,9 +26,6 @@ public class VCICacheService { @Autowired private CacheManager cacheManager; - @Autowired - private CredentialConfigurationServiceImpl credentialConfigurationService; - @Autowired private ObjectMapper objectMapper; @@ -114,46 +109,6 @@ public void setCredentialOffer(String offerId, CredentialOfferResponse offer) { } } - /** - * Get issuer metadata from cache. If not present, load from database. - */ - public Map getIssuerMetadata() { - Cache cache = cacheManager.getCache("issuerMetadataCache"); - if (cache == null) { - throw new IllegalStateException("issuerMetadataCache not available"); - } - Cache.ValueWrapper wrapper = cache.get(METADATA_KEY); - - if (wrapper == null) { - log.info("Issuer metadata not found in cache, loading from database..."); - try { - var metadata = credentialConfigurationService.fetchCredentialIssuerMetadata("latest"); - - // Convert DTOs to Map structure - Map metadataMap = new HashMap<>(); - Map credentialConfigsMap = new HashMap<>(); - - // Convert each CredentialConfigurationSupportedDTO to Map - metadata.getCredentialConfigurationSupportedDTO().forEach((configId, configDTO) -> { - Map configMap = objectMapper.convertValue(configDTO, Map.class); - credentialConfigsMap.put(configId, configMap); - }); - metadataMap.put(Constants.CREDENTIAL_CONFIGURATIONS_SUPPORTED, credentialConfigsMap); - - // Store in cache - cache.put(METADATA_KEY, metadataMap); - - log.info("Successfully loaded and cached issuer metadata with {} configurations", credentialConfigsMap.size()); - - return metadataMap; - } catch (Exception e) { - log.error("Failed to load issuer metadata", e); - throw new IllegalStateException("Failed to load issuer metadata", e); - } - } - return (Map) wrapper.get(); - } - public boolean isCodeBlacklisted(String code) { String key = "blacklist:" + code; Cache.ValueWrapper wrapper = cacheManager.getCache("preAuthCodeCache").get(key); @@ -192,4 +147,54 @@ public Transaction setTransaction(String accessToken, Transaction vcIssuanceTran public Transaction getTransactionByToken(String accessToken) { return cacheManager.getCache(VCISSUANCE_CACHE).get(accessToken, Transaction.class); } + + /** + * Cache authorization server metadata + */ + public void setASMetadata(String serverUrl, AuthorizationServerMetadata metadata) { + String key = Constants.AS_METADATA_PREFIX + serverUrl; + Cache cache = cacheManager.getCache("asMetadataCache"); + if (cache == null) { + throw new IllegalStateException("asMetadataCache not available"); + } + cache.put(key, metadata); + log.info("Cached AS metadata for: {}", serverUrl); + } + + /** + * Get cached authorization server metadata + */ + public AuthorizationServerMetadata getASMetadata(String serverUrl) { + String key = Constants.AS_METADATA_PREFIX + serverUrl; + Cache cache = cacheManager.getCache("asMetadataCache"); + if (cache == null) { + log.error("Cache {} not available", "asMetadataCache"); + return null; + } + Cache.ValueWrapper wrapper = cache.get(key); + return wrapper != null ? (AuthorizationServerMetadata) wrapper.get() : null; + } + + /** + * Evict cached AS metadata for a specific server + */ + public void evictASMetadata(String serverUrl) { + String key = Constants.AS_METADATA_PREFIX + serverUrl; + Cache cache = cacheManager.getCache("asMetadataCache"); + if (cache != null) { + cache.evict(key); + log.info("Evicted AS metadata cache for: {}", serverUrl); + } + } + + /** + * Clear all AS metadata cache + */ + public void clearASMetadataCache() { + Cache cache = cacheManager.getCache("asMetadataCache"); + if (cache != null) { + cache.clear(); + log.info("Cleared all AS metadata cache"); + } + } } \ No newline at end of file From 17ab350036106ba5d5127e135fdf2a835f19f70f Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Wed, 17 Dec 2025 12:38:25 +0530 Subject: [PATCH 05/13] [INJICERT-1239] feat(tests): enhance test coverage for credential configuration and authorization server services Signed-off-by: amaydixit11 --- .../io/mosip/certify/VCICacheServiceTest.java | 33 - .../PreAuthorizedCodeControllerTest.java | 541 +++++------ .../controller/WellKnownControllerTest.java | 12 +- ...redentialConfigurationServiceImplTest.java | 190 ++-- .../PreAuthorizedCodeServiceTest.java | 900 +++++++++--------- 5 files changed, 841 insertions(+), 835 deletions(-) diff --git a/certify-service/src/test/java/io/mosip/certify/VCICacheServiceTest.java b/certify-service/src/test/java/io/mosip/certify/VCICacheServiceTest.java index 3492a606..651c105c 100644 --- a/certify-service/src/test/java/io/mosip/certify/VCICacheServiceTest.java +++ b/certify-service/src/test/java/io/mosip/certify/VCICacheServiceTest.java @@ -118,32 +118,6 @@ public void getCredentialOffer_Success() { assertEquals(offer, result); } - @Test - public void getIssuerMetadata_CacheHit() { - Map metadata = new HashMap<>(); - metadata.put("key", "value"); - Cache.ValueWrapper wrapper = mock(Cache.ValueWrapper.class); - when(wrapper.get()).thenReturn(metadata); - when(cache.get("metadata")).thenReturn(wrapper); - - Map result = vciCacheService.getIssuerMetadata(); - assertEquals(metadata, result); - verify(credentialConfigurationService, never()).fetchCredentialIssuerMetadata(anyString()); - } - - @Test - public void getIssuerMetadata_CacheMiss() { - when(cache.get("metadata")).thenReturn(null); - CredentialIssuerMetadataVD13DTO metadataDTO = new CredentialIssuerMetadataVD13DTO(); - metadataDTO.setCredentialConfigurationSupportedDTO(new HashMap<>()); - when(credentialConfigurationService.fetchCredentialIssuerMetadata("latest")).thenReturn(metadataDTO); - - Map result = vciCacheService.getIssuerMetadata(); - assertNotNull(result); - verify(credentialConfigurationService).fetchCredentialIssuerMetadata("latest"); - verify(cache).put(eq("metadata"), anyMap()); - } - @Test public void validateCacheConfiguration_Simple() { ReflectionTestUtils.setField(vciCacheService, "cacheType", "simple"); @@ -201,13 +175,6 @@ public void setCredentialOffer_WhenCacheIsNull_ThrowsIllegalStateException() { vciCacheService.setCredentialOffer("test-offer-id", new CredentialOfferResponse()); } - @Test(expected = IllegalStateException.class) - public void getIssuerMetadata_WhenCacheIsNull_ThrowsIllegalStateException() { - when(cacheManager.getCache("issuerMetadataCache")).thenReturn(null); - - vciCacheService.getIssuerMetadata(); - } - @Test public void getCredentialOffer_WhenNotFound_ReturnsNull() { String offerId = "test-offer-id"; diff --git a/certify-service/src/test/java/io/mosip/certify/controller/PreAuthorizedCodeControllerTest.java b/certify-service/src/test/java/io/mosip/certify/controller/PreAuthorizedCodeControllerTest.java index f65c4929..d336b18b 100644 --- a/certify-service/src/test/java/io/mosip/certify/controller/PreAuthorizedCodeControllerTest.java +++ b/certify-service/src/test/java/io/mosip/certify/controller/PreAuthorizedCodeControllerTest.java @@ -34,282 +34,267 @@ @WebMvcTest(value = PreAuthorizedCodeController.class) public class PreAuthorizedCodeControllerTest { - @Autowired - MockMvc mockMvc; - - @MockBean - PreAuthorizedCodeService preAuthorizedCodeService; - - // Required by AccessTokenValidationFilter which is loaded in WebMvcTest context - @MockBean - io.mosip.certify.core.dto.ParsedAccessToken parsedAccessToken; - - // Required by audit aspects/configuration - @MockBean - AuditPlugin auditWrapper; - - ObjectMapper objectMapper = new ObjectMapper(); - - @Test - public void generatePreAuthorizedCode_Success() throws Exception { - PreAuthorizedRequest request = new PreAuthorizedRequest(); - request.setCredentialConfigurationId("test-config"); - Map claims = new HashMap<>(); - claims.put("name", "John"); - request.setClaims(claims); - - String expectedUri = "openid-credential-offer://?credential_offer_uri=test"; - Mockito.when(preAuthorizedCodeService.generatePreAuthorizedCode(Mockito.any(PreAuthorizedRequest.class))) - .thenReturn(expectedUri); - - mockMvc.perform(post("/pre-authorized-data") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.credential_offer_uri").value(expectedUri)); - } - - @Test - public void generatePreAuthorizedCode_Failure_If_MissingConfigId() throws Exception { - PreAuthorizedRequest request = new PreAuthorizedRequest(); - Map claims = new HashMap<>(); - claims.put("name", "John"); - request.setClaims(claims); - // Missing credentialConfigurationId - - mockMvc.perform(post("/pre-authorized-data") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value("Credential configuration ID is required")); - } - - @Test - public void generatePreAuthorizedCode_Failure_If_MissingClaims() throws Exception { - PreAuthorizedRequest request = new PreAuthorizedRequest(); - request.setCredentialConfigurationId("test-config"); - // Missing claims - - mockMvc.perform(post("/pre-authorized-data") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value("Claims are required")); - } - - @Test - public void getCredentialOffer_Success() throws Exception { - String validUuid = "550e8400-e29b-41d4-a716-446655440000"; - CredentialOfferResponse offer = CredentialOfferResponse.builder() - .credentialIssuer("https://issuer.com") - .credentialConfigurationIds(Collections.singletonList("test-config")) - .build(); - - Mockito.when(preAuthorizedCodeService.getCredentialOffer(validUuid)) - .thenReturn(offer); - - mockMvc.perform(get("/credential-offer-data/{offer_id}", validUuid) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.credential_issuer").value("https://issuer.com")) - .andExpect(jsonPath("$.credential_configuration_ids[0]").value("test-config")); - } - - @Test - public void getCredentialOffer_InvalidUuidFormat() throws Exception { - String invalidUuid = "not-a-valid-uuid"; - - Mockito.when(preAuthorizedCodeService.getCredentialOffer(invalidUuid)) - .thenThrow(new InvalidRequestException(ErrorConstants.INVALID_OFFER_ID_FORMAT)); - - mockMvc.perform(get("/credential-offer-data/{offer_id}", invalidUuid) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_OFFER_ID_FORMAT)); - } - - @Test - public void getCredentialOffer_NotFound() throws Exception { - String validUuid = "550e8400-e29b-41d4-a716-446655440000"; - - Mockito.when(preAuthorizedCodeService.getCredentialOffer(validUuid)) - .thenThrow(new CertifyException("offer_not_found", "Credential offer not found or expired")); - - mockMvc.perform(get("/credential-offer-data/{offer_id}", validUuid) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value("offer_not_found")); - } - - // Tests for /token endpoint - - @Test - public void token_Success() throws Exception { - TokenRequest request = new TokenRequest(); - request.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - request.setPreAuthorizedCode("test-pre-auth-code"); - - TokenResponse expectedResponse = TokenResponse.builder() - .accessToken("at_test_access_token") - .tokenType("Bearer") - .expiresIn(600) - .cNonce("test-nonce") - .cNonceExpiresIn(300) - .build(); - - Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) - .thenReturn(expectedResponse); - - mockMvc.perform(post("/token") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").value("at_test_access_token")) - .andExpect(jsonPath("$.token_type").value("Bearer")) - .andExpect(jsonPath("$.expires_in").value(600)) - .andExpect(jsonPath("$.c_nonce").value("test-nonce")) - .andExpect(jsonPath("$.c_nonce_expires_in").value(300)); - } - - @Test - public void token_WithTxCode_Success() throws Exception { - TokenRequest request = new TokenRequest(); - request.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - request.setPreAuthorizedCode("test-pre-auth-code"); - request.setTxCode("1234"); - - TokenResponse expectedResponse = TokenResponse.builder() - .accessToken("at_test_access_token") - .tokenType("Bearer") - .expiresIn(600) - .cNonce("test-nonce") - .cNonceExpiresIn(300) - .build(); - - Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) - .thenReturn(expectedResponse); - - mockMvc.perform(post("/token") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").value("at_test_access_token")) - .andExpect(jsonPath("$.token_type").value("Bearer")); - } - - @Test - public void token_UnsupportedGrantType() throws Exception { - TokenRequest request = new TokenRequest(); - request.setGrantType("invalid_grant_type"); - request.setPreAuthorizedCode("test-pre-auth-code"); - - Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) - .thenThrow(new CertifyException(ErrorConstants.UNSUPPORTED_GRANT_TYPE, "Grant type not supported")); - - mockMvc.perform(post("/token") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.UNSUPPORTED_GRANT_TYPE)); - } - - @Test - public void token_InvalidPreAuthCode() throws Exception { - TokenRequest request = new TokenRequest(); - request.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - request.setPreAuthorizedCode("invalid-code"); - - Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) - .thenThrow(new CertifyException(ErrorConstants.INVALID_GRANT, "Pre-authorized code not found")); - - mockMvc.perform(post("/token") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_GRANT)); - } - - @Test - public void token_ExpiredPreAuthCode() throws Exception { - TokenRequest request = new TokenRequest(); - request.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - request.setPreAuthorizedCode("expired-code"); - - Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) - .thenThrow(new CertifyException("pre_auth_code_expired", "Pre-authorized code has expired")); - - mockMvc.perform(post("/token") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value("pre_auth_code_expired")); - } - - @Test - public void token_AlreadyUsedPreAuthCode() throws Exception { - TokenRequest request = new TokenRequest(); - request.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - request.setPreAuthorizedCode("used-code"); - - Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) - .thenThrow(new CertifyException("pre_auth_code_already_used", "Pre-authorized code has already been used")); - - mockMvc.perform(post("/token") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value("pre_auth_code_already_used")); - } - - @Test - public void token_TxCodeRequired() throws Exception { - TokenRequest request = new TokenRequest(); - request.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - request.setPreAuthorizedCode("test-code"); - // txCode not provided but required - - Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) - .thenThrow(new CertifyException("tx_code_required", "Transaction code is required for this pre-authorized code")); - - mockMvc.perform(post("/token") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value("tx_code_required")); - } - - @Test - public void token_TxCodeMismatch() throws Exception { - TokenRequest request = new TokenRequest(); - request.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - request.setPreAuthorizedCode("test-code"); - request.setTxCode("wrong-code"); - - Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) - .thenThrow(new CertifyException("tx_code_mismatch", "Transaction code does not match")); - - mockMvc.perform(post("/token") - .content(objectMapper.writeValueAsBytes(request)) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors - .andExpect(jsonPath("$.errors").isArray()) - .andExpect(jsonPath("$.errors[0].errorCode").value("tx_code_mismatch")); - } + @Autowired + MockMvc mockMvc; + + @MockBean + PreAuthorizedCodeService preAuthorizedCodeService; + + // Required by AccessTokenValidationFilter which is loaded in WebMvcTest context + @MockBean + io.mosip.certify.core.dto.ParsedAccessToken parsedAccessToken; + + // Required by audit aspects/configuration + @MockBean + AuditPlugin auditWrapper; + + ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void generatePreAuthorizedCode_Success() throws Exception { + PreAuthorizedRequest request = new PreAuthorizedRequest(); + request.setCredentialConfigurationId("test-config"); + Map claims = new HashMap<>(); + claims.put("name", "John"); + request.setClaims(claims); + + String expectedUri = "openid-credential-offer://?credential_offer_uri=test"; + Mockito.when(preAuthorizedCodeService + .generatePreAuthorizedCode(Mockito.any(PreAuthorizedRequest.class))) + .thenReturn(expectedUri); + + mockMvc.perform(post("/pre-authorized-data") + .content(objectMapper.writeValueAsBytes(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.credential_offer_uri").value(expectedUri)); + } + + @Test + public void generatePreAuthorizedCode_Failure_If_MissingConfigId() throws Exception { + PreAuthorizedRequest request = new PreAuthorizedRequest(); + Map claims = new HashMap<>(); + claims.put("name", "John"); + request.setClaims(claims); + // Missing credentialConfigurationId + + mockMvc.perform(post("/pre-authorized-data") + .content(objectMapper.writeValueAsBytes(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode") + .value("Credential configuration ID is required")); + } + + @Test + public void generatePreAuthorizedCode_Failure_If_MissingClaims() throws Exception { + PreAuthorizedRequest request = new PreAuthorizedRequest(); + request.setCredentialConfigurationId("test-config"); + // Missing claims + + mockMvc.perform(post("/pre-authorized-data") + .content(objectMapper.writeValueAsBytes(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode").value("Claims are required")); + } + + @Test + public void getCredentialOffer_Success() throws Exception { + String validUuid = "550e8400-e29b-41d4-a716-446655440000"; + CredentialOfferResponse offer = CredentialOfferResponse.builder() + .credentialIssuer("https://issuer.com") + .credentialConfigurationIds(Collections.singletonList("test-config")) + .build(); + + Mockito.when(preAuthorizedCodeService.getCredentialOffer(validUuid)) + .thenReturn(offer); + + mockMvc.perform(get("/credential-offer-data/{offer_id}", validUuid) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.credential_issuer").value("https://issuer.com")) + .andExpect(jsonPath("$.credential_configuration_ids[0]").value("test-config")); + } + + @Test + public void getCredentialOffer_InvalidUuidFormat() throws Exception { + String invalidUuid = "not-a-valid-uuid"; + + Mockito.when(preAuthorizedCodeService.getCredentialOffer(invalidUuid)) + .thenThrow(new InvalidRequestException(ErrorConstants.INVALID_OFFER_ID_FORMAT)); + + mockMvc.perform(get("/credential-offer-data/{offer_id}", invalidUuid) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode") + .value(ErrorConstants.INVALID_OFFER_ID_FORMAT)); + } + + @Test + public void getCredentialOffer_NotFound() throws Exception { + String validUuid = "550e8400-e29b-41d4-a716-446655440000"; + + Mockito.when(preAuthorizedCodeService.getCredentialOffer(validUuid)) + .thenThrow(new CertifyException("offer_not_found", + "Credential offer not found or expired")); + + mockMvc.perform(get("/credential-offer-data/{offer_id}", validUuid) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode").value("offer_not_found")); + } + + // Tests for /token endpoint + + @Test + public void token_Success() throws Exception { + TokenResponse expectedResponse = TokenResponse.builder() + .accessToken("at_test_access_token") + .tokenType("Bearer") + .expiresIn(600) + .cNonce("test-nonce") + .cNonceExpiresIn(300) + .build(); + + Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) + .thenReturn(expectedResponse); + + mockMvc.perform(post("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE) + .param("pre-authorized_code", "test-pre-auth-code") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.access_token").value("at_test_access_token")) + .andExpect(jsonPath("$.token_type").value("Bearer")) + .andExpect(jsonPath("$.expires_in").value(600)) + .andExpect(jsonPath("$.c_nonce").value("test-nonce")) + .andExpect(jsonPath("$.c_nonce_expires_in").value(300)); + } + + @Test + public void token_WithTxCode_Success() throws Exception { + TokenResponse expectedResponse = TokenResponse.builder() + .accessToken("at_test_access_token") + .tokenType("Bearer") + .expiresIn(600) + .cNonce("test-nonce") + .cNonceExpiresIn(300) + .build(); + + Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) + .thenReturn(expectedResponse); + + mockMvc.perform(post("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE) + .param("pre-authorized_code", "test-pre-auth-code") + .param("tx_code", "1234") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.access_token").value("at_test_access_token")) + .andExpect(jsonPath("$.token_type").value("Bearer")); + } + + @Test + public void token_UnsupportedGrantType() throws Exception { + Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) + .thenThrow(new CertifyException(ErrorConstants.UNSUPPORTED_GRANT_TYPE, + "Grant type not supported")); + + mockMvc.perform(post("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", "invalid_grant_type") + .param("pre-authorized_code", "test-pre-auth-code") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode") + .value(ErrorConstants.UNSUPPORTED_GRANT_TYPE)); + } + + @Test + public void token_InvalidPreAuthCode() throws Exception { + Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) + .thenThrow(new CertifyException(ErrorConstants.INVALID_GRANT, + "Pre-authorized code not found")); + + mockMvc.perform(post("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE) + .param("pre-authorized_code", "invalid-code") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode").value(ErrorConstants.INVALID_GRANT)); + } + + @Test + public void token_ExpiredPreAuthCode() throws Exception { + Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) + .thenThrow(new CertifyException("pre_auth_code_expired", + "Pre-authorized code has expired")); + + mockMvc.perform(post("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE) + .param("pre-authorized_code", "expired-code") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode").value("pre_auth_code_expired")); + } + + @Test + public void token_AlreadyUsedPreAuthCode() throws Exception { + Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) + .thenThrow(new CertifyException("pre_auth_code_already_used", + "Pre-authorized code has already been used")); + + mockMvc.perform(post("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE) + .param("pre-authorized_code", "used-code") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode").value("pre_auth_code_already_used")); + } + + @Test + public void token_TxCodeRequired() throws Exception { + Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) + .thenThrow(new CertifyException("tx_code_required", + "Transaction code is required for this pre-authorized code")); + + mockMvc.perform(post("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE) + .param("pre-authorized_code", "test-code") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode").value("tx_code_required")); + } + + @Test + public void token_TxCodeMismatch() throws Exception { + Mockito.when(preAuthorizedCodeService.exchangePreAuthorizedCode(Mockito.any(TokenRequest.class))) + .thenThrow(new CertifyException("tx_code_mismatch", "Transaction code does not match")); + + mockMvc.perform(post("/token") + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .param("grant_type", Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE) + .param("pre-authorized_code", "test-code") + .param("tx_code", "wrong-code") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) // ExceptionHandler returns 200 OK with errors + .andExpect(jsonPath("$.errors").isArray()) + .andExpect(jsonPath("$.errors[0].errorCode").value("tx_code_mismatch")); + } } diff --git a/certify-service/src/test/java/io/mosip/certify/controller/WellKnownControllerTest.java b/certify-service/src/test/java/io/mosip/certify/controller/WellKnownControllerTest.java index 77410ded..181814cf 100644 --- a/certify-service/src/test/java/io/mosip/certify/controller/WellKnownControllerTest.java +++ b/certify-service/src/test/java/io/mosip/certify/controller/WellKnownControllerTest.java @@ -36,6 +36,12 @@ class WellKnownControllerTest { @MockBean private ParsedAccessToken parsedAccessToken; + @MockBean + private io.mosip.certify.api.spi.AuditPlugin auditWrapper; + + @MockBean + private io.mosip.certify.services.AuthorizationServerService authorizationServerService; + @InjectMocks private WellKnownController wellKnownController; @@ -64,7 +70,8 @@ void getCredentialIssuerMetadata_emptyVersion_defaultsToLatest() throws Exceptio @Test void getCredentialIssuerMetadata_unsupportedVersion_returnsError() throws Exception { - when(credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported")).thenThrow( new CertifyException("UNSUPPORTED_VERSION", "Unsupported version")); + when(credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported")) + .thenThrow(new CertifyException("UNSUPPORTED_VERSION", "Unsupported version")); mockMvc.perform(get("/.well-known/openid-credential-issuer?version=unsupported")) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.errors[0].errorCode").value("UNSUPPORTED_VERSION")); @@ -89,7 +96,8 @@ void getDIDDocument_notFound_returnsEmpty() throws Exception { @Test void getDIDDocument_serviceThrowsException_returnsError() throws Exception { - when(vcIssuanceService.getDIDDocument()).thenThrow(new InvalidRequestException("unsupported_in_current_plugin_mode")); + when(vcIssuanceService.getDIDDocument()) + .thenThrow(new InvalidRequestException("unsupported_in_current_plugin_mode")); mockMvc.perform(get("/.well-known/did.json")) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.errors[0].errorCode").value("unsupported_in_current_plugin_mode")); diff --git a/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java b/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java index 0ed32fa0..aa6584a5 100644 --- a/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java +++ b/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java @@ -38,6 +38,9 @@ public class CredentialConfigurationServiceImplTest { @Mock private CredentialConfigMapper credentialConfigMapper; + @Mock + private AuthorizationServerService authServerService; + @InjectMocks private CredentialConfigurationServiceImpl credentialConfigurationService; @@ -52,7 +55,7 @@ public void setup() { Map>> keyAliasMapper = new HashMap<>(); keyAliasMapper.put("EdDSA", List.of( List.of("TEST2019", "TEST2019-REF"))); -// keyAliasMapper.put("RS256", List.of()); + // keyAliasMapper.put("RS256", List.of()); MockitoAnnotations.openMocks(this); credentialConfig = new CredentialConfig(); @@ -69,7 +72,8 @@ public void setup() { credentialConfig.setScope("test_vc_ldp"); credentialConfig.setCryptographicBindingMethodsSupported(List.of("did:jwk")); credentialConfig.setCredentialSigningAlgValuesSupported(List.of("Ed25519Signature2020")); - credentialConfig.setCredentialSubject(Map.of("name", new CredentialSubjectParameters(List.of(new CredentialSubjectParameters.Display("Full Name", "en"))))); + credentialConfig.setCredentialSubject(Map.of("name", + new CredentialSubjectParameters(List.of(new CredentialSubjectParameters.Display("Full Name", "en"))))); credentialConfig.setKeyManagerAppId("TEST2019"); credentialConfig.setKeyManagerRefId("TEST2019-REF"); credentialConfig.setSignatureCryptoSuite("Ed25519Signature2020"); @@ -80,11 +84,13 @@ public void setup() { credentialConfigurationDTO.setVcTemplate("test_template"); credentialConfigurationDTO.setCredentialFormat("ldp_vc"); credentialConfigurationDTO.setContextURLs(List.of("https://www.w3.org/2018/credentials/v1")); - credentialConfigurationDTO.setCredentialTypes(Arrays.asList("VerifiableCredential", "TestVerifiableCredential")); + credentialConfigurationDTO + .setCredentialTypes(Arrays.asList("VerifiableCredential", "TestVerifiableCredential")); credentialConfigurationDTO.setSignatureCryptoSuite("Ed25519Signature2020"); credentialConfigurationDTO.setKeyManagerAppId("TEST2019"); credentialConfigurationDTO.setKeyManagerRefId("TEST2019-REF"); - credentialConfigurationDTO.setCredentialSubjectDefinition(Map.of("name", new CredentialSubjectParametersDTO(List.of(new CredentialSubjectParametersDTO.Display("Full Name", "en"))))); + credentialConfigurationDTO.setCredentialSubjectDefinition(Map.of("name", new CredentialSubjectParametersDTO( + List.of(new CredentialSubjectParametersDTO.Display("Full Name", "en"))))); ReflectionTestUtils.setField(credentialConfigurationService, "credentialIssuer", "http://example.com/"); ReflectionTestUtils.setField(credentialConfigurationService, "authUrl", "http://auth.com"); @@ -94,10 +100,15 @@ public void setup() { Map> credentialSigningMap = new LinkedHashMap<>(); credentialSigningMap.put("Ed25519Signature2020", List.of("EdDSA")); credentialSigningMap.put("RsaSignature2018", List.of("RS256")); - ReflectionTestUtils.setField(credentialConfigurationService, "cryptographicBindingMethodsSupportedMap", new LinkedHashMap<>()); - ReflectionTestUtils.setField(credentialConfigurationService, "credentialSigningAlgValuesSupportedMap", credentialSigningMap); + ReflectionTestUtils.setField(credentialConfigurationService, "cryptographicBindingMethodsSupportedMap", + new LinkedHashMap<>()); + ReflectionTestUtils.setField(credentialConfigurationService, "credentialSigningAlgValuesSupportedMap", + credentialSigningMap); ReflectionTestUtils.setField(credentialConfigurationService, "proofTypesSupported", new LinkedHashMap<>()); ReflectionTestUtils.setField(credentialConfigurationService, "keyAliasMapper", keyAliasMapper); + + // Mock authServerService to return the expected authorization server URLs + when(authServerService.getAllAuthorizationServerUrls()).thenReturn(List.of("http://auth.com")); } @Test @@ -109,7 +120,8 @@ public void addNewCredentialConfig_Success() { when(credentialConfigMapper.toEntity(any(CredentialConfigurationDTO.class))).thenReturn(credentialConfig); when(credentialConfigRepository.save(any(CredentialConfig.class))).thenReturn(credentialConfig); - CredentialConfigResponse credentialConfigResponse = credentialConfigurationService.addCredentialConfiguration(credentialConfigurationDTO); + CredentialConfigResponse credentialConfigResponse = credentialConfigurationService + .addCredentialConfiguration(credentialConfigurationDTO); Assert.assertNotNull(credentialConfigResponse); Assert.assertNotNull(credentialConfigResponse.getId()); @@ -126,10 +138,10 @@ public void addCredentialConfiguration_DataProviderMode_VcTemplateNull_ThrowsExc dto.setVcTemplate(null); // or "" // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, () -> - credentialConfigurationService.addCredentialConfiguration(dto) - ); - org.junit.Assert.assertEquals("Credential Template is mandatory for the DataProvider plugin issuer.", exception.getMessage()); + CertifyException exception = assertThrows(CertifyException.class, + () -> credentialConfigurationService.addCredentialConfiguration(dto)); + org.junit.Assert.assertEquals("Credential Template is mandatory for the DataProvider plugin issuer.", + exception.getMessage()); } @Test @@ -137,7 +149,8 @@ public void getCredentialConfigById_Success() { Optional optional = Optional.of(credentialConfig); when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(optional); when(credentialConfigMapper.toDto(any(CredentialConfig.class))).thenReturn(credentialConfigurationDTO); - CredentialConfigurationDTO credentialConfigurationDTOResponse = credentialConfigurationService.getCredentialConfigurationById("test"); + CredentialConfigurationDTO credentialConfigurationDTOResponse = credentialConfigurationService + .getCredentialConfigurationById("test"); Assert.assertNotNull(credentialConfigurationDTOResponse); Assert.assertNotNull(credentialConfigurationDTOResponse.getCredentialTypes()); @@ -145,8 +158,10 @@ public void getCredentialConfigById_Success() { Assert.assertNotNull(credentialConfigurationDTOResponse.getContextURLs()); Assert.assertNotNull(credentialConfigurationDTOResponse.getVcTemplate()); Assert.assertEquals("test_template", credentialConfigurationDTOResponse.getVcTemplate()); - Assert.assertEquals(List.of("https://www.w3.org/2018/credentials/v1"), credentialConfigurationDTOResponse.getContextURLs()); - Assert.assertEquals(Arrays.asList("VerifiableCredential", "TestVerifiableCredential"), credentialConfigurationDTOResponse.getCredentialTypes()); + Assert.assertEquals(List.of("https://www.w3.org/2018/credentials/v1"), + credentialConfigurationDTOResponse.getContextURLs()); + Assert.assertEquals(Arrays.asList("VerifiableCredential", "TestVerifiableCredential"), + credentialConfigurationDTOResponse.getCredentialTypes()); Assert.assertEquals("ldp_vc", credentialConfigurationDTOResponse.getCredentialFormat()); } @@ -155,8 +170,8 @@ public void getCredentialConfigurationById_ConfigNotFound() { when(credentialConfigRepository.findByCredentialConfigKeyId("12345678")) .thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> - credentialConfigurationService.getCredentialConfigurationById("12345678")); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, + () -> credentialConfigurationService.getCredentialConfigurationById("12345678")); assertEquals("Configuration not found with the provided id: " + "12345678", exception.getMessage()); } @@ -165,10 +180,10 @@ public void getCredentialConfigurationById_ConfigNotFound() { public void getCredentialConfigurationById_ConfigNotActive_ThrowsException() { CredentialConfig inactiveConfig = new CredentialConfig(); inactiveConfig.setStatus("inactive"); // Not Constants.ACTIVE - when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(Optional.of(inactiveConfig)); - CertifyException exception = assertThrows(CertifyException.class, () -> - credentialConfigurationService.getCredentialConfigurationById("test-id") - ); + when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())) + .thenReturn(Optional.of(inactiveConfig)); + CertifyException exception = assertThrows(CertifyException.class, + () -> credentialConfigurationService.getCredentialConfigurationById("test-id")); assertEquals("Configuration not active.", exception.getMessage()); } @@ -185,11 +200,9 @@ public void updateExistingCredentialConfig_Success() { mockCredentialConfig.setSdJwtVct("test-vct"); mockCredentialConfig.setSignatureAlgo("ES256"); - Optional optionalConfig = Optional.of(mockCredentialConfig); when(credentialConfigRepository.findByCredentialConfigKeyId(eq(expectedId))).thenReturn(optionalConfig); - CredentialConfigurationDTO mockDto = new CredentialConfigurationDTO(); // Create a valid DTO for validation that will be returned by toDto @@ -203,11 +216,13 @@ public void updateExistingCredentialConfig_Success() { // Mock the mapper methods when(credentialConfigMapper.toDto(any(CredentialConfig.class))).thenReturn(validationDto); - doNothing().when(credentialConfigMapper).updateEntityFromDto(any(CredentialConfigurationDTO.class), any(CredentialConfig.class)); + doNothing().when(credentialConfigMapper).updateEntityFromDto(any(CredentialConfigurationDTO.class), + any(CredentialConfig.class)); when(credentialConfigRepository.save(any(CredentialConfig.class))).thenReturn(mockCredentialConfig); // --- Act --- - CredentialConfigResponse credentialConfigResponse = credentialConfigurationService.updateCredentialConfiguration(expectedId, mockDto); + CredentialConfigResponse credentialConfigResponse = credentialConfigurationService + .updateCredentialConfiguration(expectedId, mockDto); // --- Assert --- Assert.assertNotNull(credentialConfigResponse); @@ -228,8 +243,9 @@ public void updateExistingCredentialConfiguration_ConfigNotFound() { when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())) .thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> - credentialConfigurationService.updateCredentialConfiguration("12345678", new CredentialConfigurationDTO())); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, + () -> credentialConfigurationService.updateCredentialConfiguration("12345678", + new CredentialConfigurationDTO())); assertEquals("Configuration not found with the provided id: " + "12345678", exception.getMessage()); } @@ -250,8 +266,8 @@ public void deleteCredentialConfiguration_ConfigNotFound() { when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())) .thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> - credentialConfigurationService.deleteCredentialConfigurationById("12345678")); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, + () -> credentialConfigurationService.deleteCredentialConfigurationById("12345678")); assertEquals("Configuration not found with the provided id: 12345678", exception.getMessage()); } @@ -298,12 +314,12 @@ public void fetchCredentialIssuerMetadata_SigningAlgValuesSupported_UsesSignatur dto.setCredentialFormat("ldp_vc"); when(credentialConfigMapper.toDto(config)).thenReturn(dto); - CredentialIssuerMetadataDTO result = credentialConfigurationService.fetchCredentialIssuerMetadata("latest"); Assert.assertNotNull(result); Assert.assertTrue(result.getCredentialConfigurationSupportedDTO().containsKey("test-credential")); - CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO().get("test-credential"); + CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO() + .get("test-credential"); Assert.assertEquals(List.of("ES256"), supportedDTO.getCredentialSigningAlgValuesSupported()); } @@ -327,7 +343,8 @@ public void fetchCredentialIssuerMetadata_SigningAlgValuesSupported_UsesSignatur Assert.assertNotNull(result); Assert.assertTrue(result.getCredentialConfigurationSupportedDTO().containsKey("sdjwt-credential")); - CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO().get("sdjwt-credential"); + CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO() + .get("sdjwt-credential"); Assert.assertEquals(List.of("ES256"), supportedDTO.getCredentialSigningAlgValuesSupported()); } @@ -367,9 +384,8 @@ public void fetchCredentialIssuerMetadata_invalidVersion() { // Call with specific version - CertifyException ex = assertThrows(CertifyException.class, () -> - credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported_version") - ); + CertifyException ex = assertThrows(CertifyException.class, + () -> credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported_version")); assertEquals("Unsupported version: unsupported_version", ex.getMessage()); } @@ -404,7 +420,8 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { mdocConfig.setStatus("active"); mdocConfig.setCredentialFormat("mso_mdoc"); - mdocConfig.setMsoMdocClaims(Map.of("firstName", Map.of( "First Name", new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test","en")))))); + mdocConfig.setMsoMdocClaims(Map.of("firstName", Map.of("First Name", + new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test", "en")))))); mdocConfig.setDocType("docType1"); List credentialConfigList = List.of(mdocConfig); @@ -415,7 +432,8 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { mdocDTO.setCredentialFormat("mso_mdoc"); mdocDTO.setCredentialConfigKeyId("mdoc-credential"); mdocDTO.setScope("mdoc_scope"); - mdocDTO.setMsoMdocClaims(Map.of("firstName", Map.of( "First Name", new ClaimsDisplayFieldsConfigDTO(List.of(new ClaimsDisplayFieldsConfigDTO.Display("Test","en")))))); + mdocDTO.setMsoMdocClaims(Map.of("firstName", Map.of("First Name", + new ClaimsDisplayFieldsConfigDTO(List.of(new ClaimsDisplayFieldsConfigDTO.Display("Test", "en")))))); mdocDTO.setDocType("docType1"); when(credentialConfigMapper.toDto(mdocConfig)).thenReturn(mdocDTO); @@ -426,9 +444,15 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { // Verify MSO_MDOC configuration Assert.assertNotNull(result.getCredentialConfigurationSupportedDTO()); Assert.assertEquals(1, result.getCredentialConfigurationSupportedDTO().size()); - Assert.assertEquals(Map.of("firstName", Map.of( "First Name", new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test","en"))))), result.getCredentialConfigurationSupportedDTO().get("mdoc-credential").getClaims()); - - CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO().get("mdoc-credential"); + Assert.assertEquals( + Map.of("firstName", + Map.of("First Name", + new ClaimsDisplayFieldsConfigs( + List.of(new ClaimsDisplayFieldsConfigs.Display("Test", "en"))))), + result.getCredentialConfigurationSupportedDTO().get("mdoc-credential").getClaims()); + + CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO() + .get("mdoc-credential"); Assert.assertNotNull(supportedDTO); Assert.assertEquals("mso_mdoc", supportedDTO.getFormat()); Assert.assertNotNull(supportedDTO.getClaims()); @@ -439,7 +463,7 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { // Add these methods to CredentialConfigurationServiceImplTest @Test - public void addNewCredentialConfig_MsoMdoc_Success(){ + public void addNewCredentialConfig_MsoMdoc_Success() { CredentialConfig mdocConfig = new CredentialConfig(); mdocConfig.setConfigId(UUID.randomUUID().toString()); mdocConfig.setCredentialConfigKeyId("mdoc-credential"); @@ -487,7 +511,6 @@ public void addNewCredentialConfig_SdJwt_Success() { when(credentialConfigMapper.toEntity(any(CredentialConfigurationDTO.class))).thenReturn(sdJwtConfig); when(credentialConfigRepository.save(any(CredentialConfig.class))).thenReturn(sdJwtConfig); - CredentialConfigResponse response = credentialConfigurationService.addCredentialConfiguration(sdJwtDTO); Assert.assertNotNull(response); @@ -502,10 +525,10 @@ public void validateCredentialConfiguration_LdpVc_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); - assertEquals("Context, credentialType and signatureCryptoSuite are mandatory for ldp_vc format", ex.getMessage()); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + assertEquals("Context, credentialType and signatureCryptoSuite are mandatory for ldp_vc format", + ex.getMessage()); } } @@ -517,9 +540,8 @@ public void validateCredentialConfiguration_LdpVc_Duplicate_ThrowsException() { try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> LdpVcCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Configuration already exists for the given context and credentialType", ex.getMessage()); } } @@ -531,9 +553,8 @@ public void validateCredentialConfiguration_MsoMdoc_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(MsoMdocCredentialConfigValidator.class)) { mocked.when(() -> MsoMdocCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Doctype and signatureCryptoSuite fields are mandatory for mso_mdoc format", ex.getMessage()); } } @@ -546,9 +567,8 @@ public void validateCredentialConfiguration_MsoMdoc_Duplicate_ThrowsException() try (var mocked = org.mockito.Mockito.mockStatic(MsoMdocCredentialConfigValidator.class)) { mocked.when(() -> MsoMdocCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> MsoMdocCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Configuration already exists for the given doctype", ex.getMessage()); } } @@ -560,9 +580,8 @@ public void validateCredentialConfiguration_SdJwt_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(SdJwtCredentialConfigValidator.class)) { mocked.when(() -> SdJwtCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Vct and signatureAlgo fields are mandatory for vc+sd-jwt format", ex.getMessage()); } } @@ -575,9 +594,8 @@ public void validateCredentialConfiguration_SdJwt_Duplicate_ThrowsException() { try (var mocked = org.mockito.Mockito.mockStatic(SdJwtCredentialConfigValidator.class)) { mocked.when(() -> SdJwtCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> SdJwtCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Configuration already exists for the given vct", ex.getMessage()); } } @@ -592,9 +610,8 @@ public void validateCredentialConfiguration_LdpVc_MissingSignatureAlgo_ThrowsExc try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> LdpVcCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Unsupported signature crypto suite: test-rdfc-2019", ex.getMessage()); } } @@ -605,10 +622,10 @@ public void validateCredentialConfiguration_MultipleCredentialStatusPurposes_Thr dto.setCredentialFormat("ldp_vc"); dto.setVcTemplate("test_template"); dto.setCredentialStatusPurposes(List.of("purpose1", "purpose2")); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1")); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", + List.of("purpose1")); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Multiple credential status purposes are not currently supported.", ex.getMessage()); } @@ -618,10 +635,10 @@ public void validateCredentialConfiguration_InvalidCredentialStatusPurpose_Throw dto.setCredentialFormat("ldp_vc"); dto.setVcTemplate("test_template"); dto.setCredentialStatusPurposes(List.of("invalid_purpose")); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); - CertifyException ex = assertThrows(CertifyException.class, () -> - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) - ); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", + List.of("purpose1", "purpose2")); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils + .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Invalid credential status purposes. Allowed values are: [purpose1, purpose2]", ex.getMessage()); } @@ -637,10 +654,12 @@ public void validateCredentialConfiguration_NullCredentialStatusPurposes_AllowsC dto.setSignatureAlgo("EdDSA"); dto.setKeyManagerAppId("TEST2019"); dto.setKeyManagerRefId("TEST2019-REF"); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", + List.of("purpose1", "purpose2")); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true); + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, + true); } } @@ -656,10 +675,12 @@ public void validateCredentialConfiguration_EmptyCredentialStatusPurposes_Allows dto.setSignatureAlgo("EdDSA"); dto.setKeyManagerAppId("TEST2019"); dto.setKeyManagerRefId("TEST2019-REF"); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", + List.of("purpose1", "purpose2")); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true); + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, + true); } } @@ -675,10 +696,12 @@ public void validateCredentialConfiguration_ValidSingleCredentialStatusPurpose_S dto.setSignatureAlgo("EdDSA"); dto.setKeyManagerAppId("TEST2019"); dto.setKeyManagerRefId("TEST2019-REF"); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", + List.of("purpose1", "purpose2")); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true); + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, + true); } } @@ -693,10 +716,10 @@ public void validateKeyAliasMapperConfiguration_KeyAliasListIsNull_ThrowsExcepti dto.setVcTemplate("test_template"); // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, () -> - credentialConfigurationService.addCredentialConfiguration(dto) - ); - assertEquals("No key chooser configuration found for the signatureAlgo: RsaSignature2018", exception.getMessage()); + CertifyException exception = assertThrows(CertifyException.class, + () -> credentialConfigurationService.addCredentialConfiguration(dto)); + assertEquals("No key chooser configuration found for the signatureAlgo: RsaSignature2018", + exception.getMessage()); } @Test @@ -712,9 +735,8 @@ public void validateKeyAliasMpperConfiguration_NoMatchingAppIdAndRefId_ThrowsExc dto.setVcTemplate("test_template"); // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, () -> - credentialConfigurationService.addCredentialConfiguration(dto) - ); + CertifyException exception = assertThrows(CertifyException.class, + () -> credentialConfigurationService.addCredentialConfiguration(dto)); assertEquals("No matching appId and refId found in the key chooser list.", exception.getMessage()); } } diff --git a/certify-service/src/test/java/io/mosip/certify/services/PreAuthorizedCodeServiceTest.java b/certify-service/src/test/java/io/mosip/certify/services/PreAuthorizedCodeServiceTest.java index 9ded2eb9..7b5c3cba 100644 --- a/certify-service/src/test/java/io/mosip/certify/services/PreAuthorizedCodeServiceTest.java +++ b/certify-service/src/test/java/io/mosip/certify/services/PreAuthorizedCodeServiceTest.java @@ -5,6 +5,7 @@ import io.mosip.certify.core.dto.*; import io.mosip.certify.core.exception.CertifyException; import io.mosip.certify.core.exception.InvalidRequestException; +import io.mosip.certify.core.spi.CredentialConfigurationService; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -15,6 +16,7 @@ import org.springframework.test.util.ReflectionTestUtils; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import static org.junit.Assert.assertThrows; @@ -25,449 +27,471 @@ @RunWith(MockitoJUnitRunner.class) public class PreAuthorizedCodeServiceTest { - @Mock - private VCICacheService vciCacheService; - - @InjectMocks - private PreAuthorizedCodeService preAuthorizedCodeService; - - private PreAuthorizedRequest request; - private Map issuerMetadata; - private Map supportedConfigs; - private Map config; - private final String CONFIG_ID = "test-config"; - - @Before - public void setup() { - ReflectionTestUtils.setField(preAuthorizedCodeService, "issuerIdentifier", "https://issuer.com"); - ReflectionTestUtils.setField(preAuthorizedCodeService, "defaultExpirySeconds", 600); - ReflectionTestUtils.setField(preAuthorizedCodeService, "minExpirySeconds", 60); - ReflectionTestUtils.setField(preAuthorizedCodeService, "maxExpirySeconds", 86400); - ReflectionTestUtils.setField(preAuthorizedCodeService, "domainUrl", "https://domain.com/"); - - request = new PreAuthorizedRequest(); - request.setCredentialConfigurationId(CONFIG_ID); - Map claims = new HashMap<>(); - claims.put("name", "John Doe"); - request.setClaims(claims); - - issuerMetadata = new HashMap<>(); - supportedConfigs = new HashMap<>(); - config = new HashMap<>(); - Map requiredClaims = new HashMap<>(); - Map nameClaim = new HashMap<>(); - nameClaim.put(Constants.MANDATORY, true); - requiredClaims.put("name", nameClaim); - config.put(Constants.CLAIMS, requiredClaims); - supportedConfigs.put(CONFIG_ID, config); - issuerMetadata.put(Constants.CREDENTIAL_CONFIGURATIONS_SUPPORTED, supportedConfigs); - - when(vciCacheService.getIssuerMetadata()).thenReturn(issuerMetadata); - } - - @Test - public void generatePreAuthorizedCode_Success() { - String result = preAuthorizedCodeService.generatePreAuthorizedCode(request); - - Assert.assertNotNull(result); - Assert.assertTrue(result.startsWith("openid-credential-offer://?credential_offer_uri=")); - verify(vciCacheService).setPreAuthCodeData(anyString(), any(PreAuthCodeData.class)); - verify(vciCacheService).setCredentialOffer(anyString(), any(CredentialOfferResponse.class)); - } - - @Test - public void generatePreAuthorizedCode_WithTxCode_Success() { - request.setTxCode("1234"); - String result = preAuthorizedCodeService.generatePreAuthorizedCode(request); - - Assert.assertNotNull(result); - verify(vciCacheService).setPreAuthCodeData(anyString(), any(PreAuthCodeData.class)); - verify(vciCacheService).setCredentialOffer(anyString(), any(CredentialOfferResponse.class)); - } - - @Test - public void generatePreAuthorizedCode_Failure_If_InvalidConfigId() { - request.setCredentialConfigurationId("invalid-id"); - - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); - - Assert.assertEquals(ErrorConstants.INVALID_CREDENTIAL_CONFIGURATION_ID, exception.getMessage()); - } - - @Test - public void generatePreAuthorizedCode_MissingMandatoryClaim() { - request.getClaims().remove("name"); - - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); - - Assert.assertEquals(ErrorConstants.MISSING_MANDATORY_CLAIM, exception.getErrorCode()); - } - - @Test - public void generatePreAuthorizedCode_UnknownClaim() { - request.getClaims().put("unknown", "value"); - - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); - - Assert.assertEquals(ErrorConstants.UNKNOWN_CLAIMS, exception.getErrorCode()); - } - - @Test - public void generatePreAuthorizedCode_ExpiryTooLow() { - request.setExpiresIn(10); - - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); - - Assert.assertEquals(ErrorConstants.INVALID_EXPIRY_RANGE, exception.getErrorCode()); - } - - @Test - public void generatePreAuthorizedCode_ExpiryTooHigh() { - request.setExpiresIn(100000); - - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); - - Assert.assertEquals(ErrorConstants.INVALID_EXPIRY_RANGE, exception.getErrorCode()); - } - - @Test - public void generatePreAuthorizedCode_RetryOnCollision_Success() { - // First attempt returns existing data (collision), second returns null - // (success) - when(vciCacheService.getPreAuthCodeData(anyString())) - .thenReturn(new PreAuthCodeData()) - .thenReturn(null); - - String result = preAuthorizedCodeService.generatePreAuthorizedCode(request); - - Assert.assertNotNull(result); - // Should have called getPreAuthCodeData 2 times - verify(vciCacheService, times(2)).getPreAuthCodeData(anyString()); - } - - @Test - public void generatePreAuthorizedCode_MaxRetriesExceeded_Fail() { - // Always returns existing data (collision) - when(vciCacheService.getPreAuthCodeData(anyString())).thenReturn(new PreAuthCodeData()); - - IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); + @Mock + private VCICacheService vciCacheService; + + @Mock + private CredentialConfigurationService credentialConfigurationService; + + @Mock + private AuthorizationServerService authServerService; + + @InjectMocks + private PreAuthorizedCodeService preAuthorizedCodeService; + + private PreAuthorizedRequest request; + private Map issuerMetadata; + private Map supportedConfigs; + private Map config; + private CredentialIssuerMetadataDTO metadataDTO; + private final String CONFIG_ID = "test-config"; + + @Before + public void setup() { + ReflectionTestUtils.setField(preAuthorizedCodeService, "issuerIdentifier", "https://issuer.com"); + ReflectionTestUtils.setField(preAuthorizedCodeService, "defaultExpirySeconds", 600); + ReflectionTestUtils.setField(preAuthorizedCodeService, "minExpirySeconds", 60); + ReflectionTestUtils.setField(preAuthorizedCodeService, "maxExpirySeconds", 86400); + ReflectionTestUtils.setField(preAuthorizedCodeService, "domainUrl", "https://domain.com/"); + + request = new PreAuthorizedRequest(); + request.setCredentialConfigurationId(CONFIG_ID); + Map claims = new HashMap<>(); + claims.put("name", "John Doe"); + request.setClaims(claims); + + issuerMetadata = new HashMap<>(); + supportedConfigs = new HashMap<>(); + config = new HashMap<>(); + Map requiredClaims = new HashMap<>(); + Map nameClaim = new HashMap<>(); + nameClaim.put(Constants.MANDATORY, true); + requiredClaims.put("name", nameClaim); + config.put(Constants.CLAIMS, requiredClaims); + supportedConfigs.put(CONFIG_ID, config); + issuerMetadata.put(Constants.CREDENTIAL_CONFIGURATIONS_SUPPORTED, supportedConfigs); + + // Setup mock for credentialConfigurationService + Map supportedDTOMap = new LinkedHashMap<>(); + CredentialConfigurationSupportedDTO configDTO = new CredentialConfigurationSupportedDTO(); + configDTO.setClaims(requiredClaims); + supportedDTOMap.put(CONFIG_ID, configDTO); + + metadataDTO = mock(CredentialIssuerMetadataDTO.class); + when(metadataDTO.getCredentialConfigurationSupportedDTO()).thenReturn(supportedDTOMap); + + // KEY FIX: Mock the credentialConfigurationService to return metadataDTO + when(credentialConfigurationService.fetchCredentialIssuerMetadata(anyString())).thenReturn(metadataDTO); + + // Setup mock for authServerService + when(authServerService.getAuthorizationServerForCredentialConfig(anyString())).thenReturn(null); + } + + @Test + public void generatePreAuthorizedCode_Success() { + String result = preAuthorizedCodeService.generatePreAuthorizedCode(request); + + Assert.assertNotNull(result); + Assert.assertTrue(result.startsWith("openid-credential-offer://?credential_offer_uri=")); + verify(vciCacheService).setPreAuthCodeData(anyString(), any(PreAuthCodeData.class)); + verify(vciCacheService).setCredentialOffer(anyString(), any(CredentialOfferResponse.class)); + } + + @Test + public void generatePreAuthorizedCode_WithTxCode_Success() { + request.setTxCode("1234"); + String result = preAuthorizedCodeService.generatePreAuthorizedCode(request); + + Assert.assertNotNull(result); + verify(vciCacheService).setPreAuthCodeData(anyString(), any(PreAuthCodeData.class)); + verify(vciCacheService).setCredentialOffer(anyString(), any(CredentialOfferResponse.class)); + } + + @Test + public void generatePreAuthorizedCode_Failure_If_InvalidConfigId() { + request.setCredentialConfigurationId("invalid-id"); + + // Update metadata mock to not include invalid-id + Map emptyMap = new LinkedHashMap<>(); + when(metadataDTO.getCredentialConfigurationSupportedDTO()).thenReturn(emptyMap); + + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); + + Assert.assertEquals(ErrorConstants.INVALID_CREDENTIAL_CONFIGURATION_ID, exception.getMessage()); + } + + @Test + public void generatePreAuthorizedCode_MissingMandatoryClaim() { + request.getClaims().remove("name"); + + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); + + Assert.assertEquals(ErrorConstants.MISSING_MANDATORY_CLAIM, exception.getErrorCode()); + } + + @Test + public void generatePreAuthorizedCode_UnknownClaim() { + request.getClaims().put("unknown", "value"); + + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); + + Assert.assertEquals(ErrorConstants.UNKNOWN_CLAIMS, exception.getErrorCode()); + } + + @Test + public void generatePreAuthorizedCode_ExpiryTooLow() { + request.setExpiresIn(10); + + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); + + Assert.assertEquals(ErrorConstants.INVALID_EXPIRY_RANGE, exception.getErrorCode()); + } + + @Test + public void generatePreAuthorizedCode_ExpiryTooHigh() { + request.setExpiresIn(100000); + + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); - Assert.assertTrue(exception.getMessage().contains("Failed to generate unique pre-authorized code")); - // Should have tried MAX_ATTEMPTS (3) - verify(vciCacheService, times(3)).getPreAuthCodeData(anyString()); - } + Assert.assertEquals(ErrorConstants.INVALID_EXPIRY_RANGE, exception.getErrorCode()); + } + + @Test + public void generatePreAuthorizedCode_RetryOnCollision_Success() { + // First attempt returns existing data (collision), second returns null (success) + when(vciCacheService.getPreAuthCodeData(anyString())) + .thenReturn(new PreAuthCodeData()) + .thenReturn(null); - // Tests for getCredentialOffer method + String result = preAuthorizedCodeService.generatePreAuthorizedCode(request); - @Test - public void getCredentialOffer_Success() { - String validUuid = "550e8400-e29b-41d4-a716-446655440000"; - CredentialOfferResponse expectedOffer = CredentialOfferResponse.builder() - .credentialIssuer("https://issuer.com") - .build(); - - when(vciCacheService.getCredentialOffer(validUuid)).thenReturn(expectedOffer); + Assert.assertNotNull(result); + // Should have called getPreAuthCodeData 2 times + verify(vciCacheService, times(2)).getPreAuthCodeData(anyString()); + } - CredentialOfferResponse result = preAuthorizedCodeService.getCredentialOffer(validUuid); - - Assert.assertNotNull(result); - Assert.assertEquals(expectedOffer, result); - verify(vciCacheService).getCredentialOffer(validUuid); - } - - @Test - public void getCredentialOffer_InvalidUuidFormat_ThrowsInvalidRequestException() { - String invalidUuid = "not-a-valid-uuid"; - - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.getCredentialOffer(invalidUuid)); - - Assert.assertEquals(ErrorConstants.INVALID_OFFER_ID_FORMAT, exception.getErrorCode()); - verify(vciCacheService, never()).getCredentialOffer(anyString()); - } - - @Test - public void getCredentialOffer_NullOfferId_ThrowsInvalidRequestException() { - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.getCredentialOffer(null)); - - Assert.assertEquals(ErrorConstants.INVALID_OFFER_ID_FORMAT, exception.getErrorCode()); - verify(vciCacheService, never()).getCredentialOffer(anyString()); - } - - @Test - public void getCredentialOffer_EmptyOfferId_ThrowsInvalidRequestException() { - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.getCredentialOffer("")); - - Assert.assertEquals(ErrorConstants.INVALID_OFFER_ID_FORMAT, exception.getErrorCode()); - verify(vciCacheService, never()).getCredentialOffer(anyString()); - } - - @Test - public void getCredentialOffer_WhitespaceOfferId_ThrowsInvalidRequestException() { - InvalidRequestException exception = assertThrows(InvalidRequestException.class, - () -> preAuthorizedCodeService.getCredentialOffer(" ")); - - Assert.assertEquals(ErrorConstants.INVALID_OFFER_ID_FORMAT, exception.getErrorCode()); - verify(vciCacheService, never()).getCredentialOffer(anyString()); - } - - @Test - public void getCredentialOffer_NotFound_ThrowsCertifyException() { - String validUuid = "550e8400-e29b-41d4-a716-446655440000"; - - when(vciCacheService.getCredentialOffer(validUuid)).thenReturn(null); - - CertifyException exception = assertThrows(CertifyException.class, - () -> preAuthorizedCodeService.getCredentialOffer(validUuid)); - - Assert.assertEquals("offer_not_found", exception.getErrorCode()); - verify(vciCacheService).getCredentialOffer(validUuid); - } - - @Test - public void exchangePreAuthorizedCode_Success() { - ReflectionTestUtils.setField(preAuthorizedCodeService, "accessTokenExpirySeconds", 600); - ReflectionTestUtils.setField(preAuthorizedCodeService, "cNonceExpirySeconds", 300); - ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); - - String preAuthCode = "test-pre-auth-code"; - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - tokenRequest.setPreAuthorizedCode(preAuthCode); - - Map claims = new HashMap<>(); - claims.put("name", "John Doe"); - - PreAuthCodeData codeData = PreAuthCodeData.builder() - .credentialConfigurationId(CONFIG_ID) - .claims(claims) - .createdAt(System.currentTimeMillis()) - .expiresAt(System.currentTimeMillis() + 600000) // expires in 10 minutes - .build(); - - when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); - when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); - when(vciCacheService.setTransaction(anyString(), any(Transaction.class))).thenReturn(null); - - TokenResponse response = preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest); - - Assert.assertNotNull(response); - Assert.assertNotNull(response.getAccessToken()); - Assert.assertTrue(response.getAccessToken().startsWith("at_")); - Assert.assertEquals("Bearer", response.getTokenType()); - Assert.assertEquals(Integer.valueOf(600), response.getExpiresIn()); - Assert.assertNotNull(response.getCNonce()); - Assert.assertEquals(Integer.valueOf(300), response.getCNonceExpiresIn()); - - verify(vciCacheService).getPreAuthCodeData(preAuthCode); - verify(vciCacheService).isCodeBlacklisted(preAuthCode); - verify(vciCacheService).blacklistPreAuthCode(preAuthCode); - verify(vciCacheService).setTransaction(anyString(), any(Transaction.class)); - } - - @Test - public void exchangePreAuthorizedCode_WithTxCode_Success() { - ReflectionTestUtils.setField(preAuthorizedCodeService, "accessTokenExpirySeconds", 600); - ReflectionTestUtils.setField(preAuthorizedCodeService, "cNonceExpirySeconds", 300); - ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); - - String preAuthCode = "test-pre-auth-code"; - String txCode = "1234"; - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - tokenRequest.setPreAuthorizedCode(preAuthCode); - tokenRequest.setTxCode(txCode); - - Map claims = new HashMap<>(); - claims.put("name", "John Doe"); - - PreAuthCodeData codeData = PreAuthCodeData.builder() - .credentialConfigurationId(CONFIG_ID) - .claims(claims) - .txnCode(txCode) - .createdAt(System.currentTimeMillis()) - .expiresAt(System.currentTimeMillis() + 600000) - .build(); - - when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); - when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); - when(vciCacheService.setTransaction(anyString(), any(Transaction.class))).thenReturn(null); - - TokenResponse response = preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest); - - Assert.assertNotNull(response); - Assert.assertNotNull(response.getAccessToken()); - verify(vciCacheService).blacklistPreAuthCode(preAuthCode); - } - - @Test - public void exchangePreAuthorizedCode_UnsupportedGrantType_ThrowsCertifyException() { - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType("invalid_grant_type"); - tokenRequest.setPreAuthorizedCode("test-code"); - - PreAuthCodeData codeData = PreAuthCodeData.builder() - .credentialConfigurationId(CONFIG_ID) - .createdAt(System.currentTimeMillis()) - .expiresAt(System.currentTimeMillis() + 600000) - .build(); - - when(vciCacheService.getPreAuthCodeData("test-code")).thenReturn(codeData); - - CertifyException exception = assertThrows(CertifyException.class, - () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); - - Assert.assertEquals(ErrorConstants.UNSUPPORTED_GRANT_TYPE, exception.getErrorCode()); - } - - @Test - public void exchangePreAuthorizedCode_InvalidPreAuthCode_ThrowsCertifyException() { - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - tokenRequest.setPreAuthorizedCode("invalid-code"); - - when(vciCacheService.getPreAuthCodeData("invalid-code")).thenReturn(null); - - CertifyException exception = assertThrows(CertifyException.class, - () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); - - Assert.assertEquals(ErrorConstants.INVALID_GRANT, exception.getErrorCode()); - } - - @Test - public void exchangePreAuthorizedCode_ExpiredCode_ThrowsCertifyException() { - ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); - - String preAuthCode = "expired-code"; - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - tokenRequest.setPreAuthorizedCode(preAuthCode); - - PreAuthCodeData codeData = PreAuthCodeData.builder() - .credentialConfigurationId(CONFIG_ID) - .createdAt(System.currentTimeMillis() - 700000) - .expiresAt(System.currentTimeMillis() - 100000) // expired - .build(); - - when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); - when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); - - CertifyException exception = assertThrows(CertifyException.class, - () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); - - Assert.assertEquals("pre_auth_code_expired", exception.getErrorCode()); - } - - @Test - public void exchangePreAuthorizedCode_AlreadyUsedCode_ThrowsCertifyException() { - ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); - - String preAuthCode = "used-code"; - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - tokenRequest.setPreAuthorizedCode(preAuthCode); - - PreAuthCodeData codeData = PreAuthCodeData.builder() - .credentialConfigurationId(CONFIG_ID) - .createdAt(System.currentTimeMillis()) - .expiresAt(System.currentTimeMillis() + 600000) - .build(); - - when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); - when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(true); - - CertifyException exception = assertThrows(CertifyException.class, - () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); - - Assert.assertEquals("pre_auth_code_already_used", exception.getErrorCode()); - } - - @Test - public void exchangePreAuthorizedCode_TxCodeRequired_ThrowsCertifyException() { - ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); - - String preAuthCode = "test-code"; - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - tokenRequest.setPreAuthorizedCode(preAuthCode); - // txCode not provided - - PreAuthCodeData codeData = PreAuthCodeData.builder() - .credentialConfigurationId(CONFIG_ID) - .txnCode("1234") // txCode is required - .createdAt(System.currentTimeMillis()) - .expiresAt(System.currentTimeMillis() + 600000) - .build(); - - when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); - when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); - - CertifyException exception = assertThrows(CertifyException.class, - () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); + @Test + public void generatePreAuthorizedCode_MaxRetriesExceeded_Fail() { + // Always returns existing data (collision) + when(vciCacheService.getPreAuthCodeData(anyString())).thenReturn(new PreAuthCodeData()); - Assert.assertEquals("tx_code_required", exception.getErrorCode()); - } - - @Test - public void exchangePreAuthorizedCode_TxCodeMismatch_ThrowsCertifyException() { - ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> preAuthorizedCodeService.generatePreAuthorizedCode(request)); - String preAuthCode = "test-code"; - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - tokenRequest.setPreAuthorizedCode(preAuthCode); - tokenRequest.setTxCode("wrong-code"); - - PreAuthCodeData codeData = PreAuthCodeData.builder() - .credentialConfigurationId(CONFIG_ID) - .txnCode("1234") // expected txCode - .createdAt(System.currentTimeMillis()) - .expiresAt(System.currentTimeMillis() + 600000) - .build(); + Assert.assertTrue(exception.getMessage().contains("Failed to generate unique pre-authorized code")); + // Should have tried MAX_ATTEMPTS (3) + verify(vciCacheService, times(3)).getPreAuthCodeData(anyString()); + } - when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); - when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); - - CertifyException exception = assertThrows(CertifyException.class, - () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); - - Assert.assertEquals("tx_code_mismatch", exception.getErrorCode()); - } - - @Test - public void exchangePreAuthorizedCode_SingleUseDisabled_DoesNotBlacklist() { - ReflectionTestUtils.setField(preAuthorizedCodeService, "accessTokenExpirySeconds", 600); - ReflectionTestUtils.setField(preAuthorizedCodeService, "cNonceExpirySeconds", 300); - ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", false); - - String preAuthCode = "test-pre-auth-code"; - TokenRequest tokenRequest = new TokenRequest(); - tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); - tokenRequest.setPreAuthorizedCode(preAuthCode); - - PreAuthCodeData codeData = PreAuthCodeData.builder() - .credentialConfigurationId(CONFIG_ID) - .claims(new HashMap<>()) - .createdAt(System.currentTimeMillis()) - .expiresAt(System.currentTimeMillis() + 600000) - .build(); - - when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); - when(vciCacheService.setTransaction(anyString(), any(Transaction.class))).thenReturn(null); - - TokenResponse response = preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest); - - Assert.assertNotNull(response); - verify(vciCacheService, never()).isCodeBlacklisted(anyString()); - verify(vciCacheService, never()).blacklistPreAuthCode(anyString()); - } -} + // Tests for getCredentialOffer method + @Test + public void getCredentialOffer_Success() { + String validUuid = "550e8400-e29b-41d4-a716-446655440000"; + CredentialOfferResponse expectedOffer = CredentialOfferResponse.builder() + .credentialIssuer("https://issuer.com") + .build(); + + when(vciCacheService.getCredentialOffer(validUuid)).thenReturn(expectedOffer); + + CredentialOfferResponse result = preAuthorizedCodeService.getCredentialOffer(validUuid); + + Assert.assertNotNull(result); + Assert.assertEquals(expectedOffer, result); + verify(vciCacheService).getCredentialOffer(validUuid); + } + + @Test + public void getCredentialOffer_InvalidUuidFormat_ThrowsInvalidRequestException() { + String invalidUuid = "not-a-valid-uuid"; + + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.getCredentialOffer(invalidUuid)); + + Assert.assertEquals(ErrorConstants.INVALID_OFFER_ID_FORMAT, exception.getErrorCode()); + verify(vciCacheService, never()).getCredentialOffer(anyString()); + } + + @Test + public void getCredentialOffer_NullOfferId_ThrowsInvalidRequestException() { + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.getCredentialOffer(null)); + + Assert.assertEquals(ErrorConstants.INVALID_OFFER_ID_FORMAT, exception.getErrorCode()); + verify(vciCacheService, never()).getCredentialOffer(anyString()); + } + + @Test + public void getCredentialOffer_EmptyOfferId_ThrowsInvalidRequestException() { + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.getCredentialOffer("")); + + Assert.assertEquals(ErrorConstants.INVALID_OFFER_ID_FORMAT, exception.getErrorCode()); + verify(vciCacheService, never()).getCredentialOffer(anyString()); + } + + @Test + public void getCredentialOffer_WhitespaceOfferId_ThrowsInvalidRequestException() { + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> preAuthorizedCodeService.getCredentialOffer(" ")); + + Assert.assertEquals(ErrorConstants.INVALID_OFFER_ID_FORMAT, exception.getErrorCode()); + verify(vciCacheService, never()).getCredentialOffer(anyString()); + } + + @Test + public void getCredentialOffer_NotFound_ThrowsCertifyException() { + String validUuid = "550e8400-e29b-41d4-a716-446655440000"; + + when(vciCacheService.getCredentialOffer(validUuid)).thenReturn(null); + + CertifyException exception = assertThrows(CertifyException.class, + () -> preAuthorizedCodeService.getCredentialOffer(validUuid)); + + Assert.assertEquals("offer_not_found", exception.getErrorCode()); + verify(vciCacheService).getCredentialOffer(validUuid); + } + + @Test + public void exchangePreAuthorizedCode_Success() { + ReflectionTestUtils.setField(preAuthorizedCodeService, "accessTokenExpirySeconds", 600); + ReflectionTestUtils.setField(preAuthorizedCodeService, "cNonceExpirySeconds", 300); + ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); + + String preAuthCode = "test-pre-auth-code"; + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + tokenRequest.setPreAuthorizedCode(preAuthCode); + + Map claims = new HashMap<>(); + claims.put("name", "John Doe"); + + PreAuthCodeData codeData = PreAuthCodeData.builder() + .credentialConfigurationId(CONFIG_ID) + .claims(claims) + .createdAt(System.currentTimeMillis()) + .expiresAt(System.currentTimeMillis() + 600000) // expires in 10 minutes + .build(); + + when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); + when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); + when(vciCacheService.setTransaction(anyString(), any(Transaction.class))).thenReturn(null); + + TokenResponse response = preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest); + + Assert.assertNotNull(response); + Assert.assertNotNull(response.getAccessToken()); + Assert.assertTrue(response.getAccessToken().startsWith("at_")); + Assert.assertEquals("Bearer", response.getTokenType()); + Assert.assertEquals(Integer.valueOf(600), response.getExpiresIn()); + Assert.assertNotNull(response.getCNonce()); + Assert.assertEquals(Integer.valueOf(300), response.getCNonceExpiresIn()); + + verify(vciCacheService).getPreAuthCodeData(preAuthCode); + verify(vciCacheService).isCodeBlacklisted(preAuthCode); + verify(vciCacheService).blacklistPreAuthCode(preAuthCode); + verify(vciCacheService).setTransaction(anyString(), any(Transaction.class)); + } + + @Test + public void exchangePreAuthorizedCode_WithTxCode_Success() { + ReflectionTestUtils.setField(preAuthorizedCodeService, "accessTokenExpirySeconds", 600); + ReflectionTestUtils.setField(preAuthorizedCodeService, "cNonceExpirySeconds", 300); + ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); + + String preAuthCode = "test-pre-auth-code"; + String txCode = "1234"; + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + tokenRequest.setPreAuthorizedCode(preAuthCode); + tokenRequest.setTxCode(txCode); + + Map claims = new HashMap<>(); + claims.put("name", "John Doe"); + + PreAuthCodeData codeData = PreAuthCodeData.builder() + .credentialConfigurationId(CONFIG_ID) + .claims(claims) + .txnCode(txCode) + .createdAt(System.currentTimeMillis()) + .expiresAt(System.currentTimeMillis() + 600000) + .build(); + + when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); + when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); + when(vciCacheService.setTransaction(anyString(), any(Transaction.class))).thenReturn(null); + + TokenResponse response = preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest); + + Assert.assertNotNull(response); + Assert.assertNotNull(response.getAccessToken()); + verify(vciCacheService).blacklistPreAuthCode(preAuthCode); + } + + @Test + public void exchangePreAuthorizedCode_UnsupportedGrantType_ThrowsCertifyException() { + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType("invalid_grant_type"); + tokenRequest.setPreAuthorizedCode("test-code"); + + PreAuthCodeData codeData = PreAuthCodeData.builder() + .credentialConfigurationId(CONFIG_ID) + .createdAt(System.currentTimeMillis()) + .expiresAt(System.currentTimeMillis() + 600000) + .build(); + + when(vciCacheService.getPreAuthCodeData("test-code")).thenReturn(codeData); + + CertifyException exception = assertThrows(CertifyException.class, + () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); + + Assert.assertEquals(ErrorConstants.UNSUPPORTED_GRANT_TYPE, exception.getErrorCode()); + } + + @Test + public void exchangePreAuthorizedCode_InvalidPreAuthCode_ThrowsCertifyException() { + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + tokenRequest.setPreAuthorizedCode("invalid-code"); + + when(vciCacheService.getPreAuthCodeData("invalid-code")).thenReturn(null); + + CertifyException exception = assertThrows(CertifyException.class, + () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); + + Assert.assertEquals(ErrorConstants.INVALID_GRANT, exception.getErrorCode()); + } + + @Test + public void exchangePreAuthorizedCode_ExpiredCode_ThrowsCertifyException() { + ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); + + String preAuthCode = "expired-code"; + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + tokenRequest.setPreAuthorizedCode(preAuthCode); + + PreAuthCodeData codeData = PreAuthCodeData.builder() + .credentialConfigurationId(CONFIG_ID) + .createdAt(System.currentTimeMillis() - 700000) + .expiresAt(System.currentTimeMillis() - 100000) // expired + .build(); + + when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); + when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); + + CertifyException exception = assertThrows(CertifyException.class, + () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); + + Assert.assertEquals("pre_auth_code_expired", exception.getErrorCode()); + } + + @Test + public void exchangePreAuthorizedCode_AlreadyUsedCode_ThrowsCertifyException() { + ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); + + String preAuthCode = "used-code"; + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + tokenRequest.setPreAuthorizedCode(preAuthCode); + + PreAuthCodeData codeData = PreAuthCodeData.builder() + .credentialConfigurationId(CONFIG_ID) + .createdAt(System.currentTimeMillis()) + .expiresAt(System.currentTimeMillis() + 600000) + .build(); + + when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); + when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(true); + + CertifyException exception = assertThrows(CertifyException.class, + () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); + + Assert.assertEquals("pre_auth_code_already_used", exception.getErrorCode()); + } + + @Test + public void exchangePreAuthorizedCode_TxCodeRequired_ThrowsCertifyException() { + ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); + + String preAuthCode = "test-code"; + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + tokenRequest.setPreAuthorizedCode(preAuthCode); + // txCode not provided + + PreAuthCodeData codeData = PreAuthCodeData.builder() + .credentialConfigurationId(CONFIG_ID) + .txnCode("1234") // txCode is required + .createdAt(System.currentTimeMillis()) + .expiresAt(System.currentTimeMillis() + 600000) + .build(); + + when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); + when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); + + CertifyException exception = assertThrows(CertifyException.class, + () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); + + Assert.assertEquals("tx_code_required", exception.getErrorCode()); + } + + @Test + public void exchangePreAuthorizedCode_TxCodeMismatch_ThrowsCertifyException() { + ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", true); + + String preAuthCode = "test-code"; + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + tokenRequest.setPreAuthorizedCode(preAuthCode); + tokenRequest.setTxCode("wrong-code"); + + PreAuthCodeData codeData = PreAuthCodeData.builder() + .credentialConfigurationId(CONFIG_ID) + .txnCode("1234") // expected txCode + .createdAt(System.currentTimeMillis()) + .expiresAt(System.currentTimeMillis() + 600000) + .build(); + + when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); + when(vciCacheService.isCodeBlacklisted(preAuthCode)).thenReturn(false); + + CertifyException exception = assertThrows(CertifyException.class, + () -> preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest)); + + Assert.assertEquals("tx_code_mismatch", exception.getErrorCode()); + } + + @Test + public void exchangePreAuthorizedCode_SingleUseDisabled_DoesNotBlacklist() { + ReflectionTestUtils.setField(preAuthorizedCodeService, "accessTokenExpirySeconds", 600); + ReflectionTestUtils.setField(preAuthorizedCodeService, "cNonceExpirySeconds", 300); + ReflectionTestUtils.setField(preAuthorizedCodeService, "singleUsePreAuthCode", false); + + String preAuthCode = "test-pre-auth-code"; + TokenRequest tokenRequest = new TokenRequest(); + tokenRequest.setGrantType(Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE); + tokenRequest.setPreAuthorizedCode(preAuthCode); + + PreAuthCodeData codeData = PreAuthCodeData.builder() + .credentialConfigurationId(CONFIG_ID) + .claims(new HashMap<>()) + .createdAt(System.currentTimeMillis()) + .expiresAt(System.currentTimeMillis() + 600000) + .build(); + + when(vciCacheService.getPreAuthCodeData(preAuthCode)).thenReturn(codeData); + when(vciCacheService.setTransaction(anyString(), any(Transaction.class))).thenReturn(null); + + TokenResponse response = preAuthorizedCodeService.exchangePreAuthorizedCode(tokenRequest); + + Assert.assertNotNull(response); + verify(vciCacheService, never()).isCodeBlacklisted(anyString()); + verify(vciCacheService, never()).blacklistPreAuthCode(anyString()); + } +} \ No newline at end of file From 498df63d72bdfd84b579161b0bec5d8cfbbc63e9 Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Wed, 17 Dec 2025 14:06:47 +0530 Subject: [PATCH 06/13] [INJICERT-1239] feat(VCICacheService): remove unused AS metadata cache methods Signed-off-by: amaydixit11 --- .../certify/services/VCICacheService.java | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/certify-service/src/main/java/io/mosip/certify/services/VCICacheService.java b/certify-service/src/main/java/io/mosip/certify/services/VCICacheService.java index 0d6831ea..832eb520 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/VCICacheService.java +++ b/certify-service/src/main/java/io/mosip/certify/services/VCICacheService.java @@ -177,27 +177,4 @@ public AuthorizationServerMetadata getASMetadata(String serverUrl) { Cache.ValueWrapper wrapper = cache.get(key); return wrapper != null ? (AuthorizationServerMetadata) wrapper.get() : null; } - - /** - * Evict cached AS metadata for a specific server - */ - public void evictASMetadata(String serverUrl) { - String key = Constants.AS_METADATA_PREFIX + serverUrl; - Cache cache = cacheManager.getCache("asMetadataCache"); - if (cache != null) { - cache.evict(key); - log.info("Evicted AS metadata cache for: {}", serverUrl); - } - } - - /** - * Clear all AS metadata cache - */ - public void clearASMetadataCache() { - Cache cache = cacheManager.getCache("asMetadataCache"); - if (cache != null) { - cache.clear(); - log.info("Cleared all AS metadata cache"); - } - } } \ No newline at end of file From 0ff5c4f9339cdb7bf5145bae18a55445319e701d Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Wed, 17 Dec 2025 14:11:30 +0530 Subject: [PATCH 07/13] [INJICERT-1239] feat(tests): simplify test setup and assertions in CredentialConfigurationServiceImplTest Signed-off-by: amaydixit11 --- ...redentialConfigurationServiceImplTest.java | 157 ++++++------------ 1 file changed, 51 insertions(+), 106 deletions(-) diff --git a/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java b/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java index aa6584a5..79e9161f 100644 --- a/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java +++ b/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java @@ -53,8 +53,7 @@ public class CredentialConfigurationServiceImplTest { @Before public void setup() { Map>> keyAliasMapper = new HashMap<>(); - keyAliasMapper.put("EdDSA", List.of( - List.of("TEST2019", "TEST2019-REF"))); + keyAliasMapper.put("EdDSA", List.of(List.of("TEST2019", "TEST2019-REF"))); // keyAliasMapper.put("RS256", List.of()); MockitoAnnotations.openMocks(this); @@ -72,8 +71,7 @@ public void setup() { credentialConfig.setScope("test_vc_ldp"); credentialConfig.setCryptographicBindingMethodsSupported(List.of("did:jwk")); credentialConfig.setCredentialSigningAlgValuesSupported(List.of("Ed25519Signature2020")); - credentialConfig.setCredentialSubject(Map.of("name", - new CredentialSubjectParameters(List.of(new CredentialSubjectParameters.Display("Full Name", "en"))))); + credentialConfig.setCredentialSubject(Map.of("name", new CredentialSubjectParameters(List.of(new CredentialSubjectParameters.Display("Full Name", "en"))))); credentialConfig.setKeyManagerAppId("TEST2019"); credentialConfig.setKeyManagerRefId("TEST2019-REF"); credentialConfig.setSignatureCryptoSuite("Ed25519Signature2020"); @@ -84,13 +82,11 @@ public void setup() { credentialConfigurationDTO.setVcTemplate("test_template"); credentialConfigurationDTO.setCredentialFormat("ldp_vc"); credentialConfigurationDTO.setContextURLs(List.of("https://www.w3.org/2018/credentials/v1")); - credentialConfigurationDTO - .setCredentialTypes(Arrays.asList("VerifiableCredential", "TestVerifiableCredential")); + credentialConfigurationDTO.setCredentialTypes(Arrays.asList("VerifiableCredential", "TestVerifiableCredential")); credentialConfigurationDTO.setSignatureCryptoSuite("Ed25519Signature2020"); credentialConfigurationDTO.setKeyManagerAppId("TEST2019"); credentialConfigurationDTO.setKeyManagerRefId("TEST2019-REF"); - credentialConfigurationDTO.setCredentialSubjectDefinition(Map.of("name", new CredentialSubjectParametersDTO( - List.of(new CredentialSubjectParametersDTO.Display("Full Name", "en"))))); + credentialConfigurationDTO.setCredentialSubjectDefinition(Map.of("name", new CredentialSubjectParametersDTO(List.of(new CredentialSubjectParametersDTO.Display("Full Name", "en"))))); ReflectionTestUtils.setField(credentialConfigurationService, "credentialIssuer", "http://example.com/"); ReflectionTestUtils.setField(credentialConfigurationService, "authUrl", "http://auth.com"); @@ -100,10 +96,8 @@ public void setup() { Map> credentialSigningMap = new LinkedHashMap<>(); credentialSigningMap.put("Ed25519Signature2020", List.of("EdDSA")); credentialSigningMap.put("RsaSignature2018", List.of("RS256")); - ReflectionTestUtils.setField(credentialConfigurationService, "cryptographicBindingMethodsSupportedMap", - new LinkedHashMap<>()); - ReflectionTestUtils.setField(credentialConfigurationService, "credentialSigningAlgValuesSupportedMap", - credentialSigningMap); + ReflectionTestUtils.setField(credentialConfigurationService, "cryptographicBindingMethodsSupportedMap", new LinkedHashMap<>()); + ReflectionTestUtils.setField(credentialConfigurationService, "credentialSigningAlgValuesSupportedMap", credentialSigningMap); ReflectionTestUtils.setField(credentialConfigurationService, "proofTypesSupported", new LinkedHashMap<>()); ReflectionTestUtils.setField(credentialConfigurationService, "keyAliasMapper", keyAliasMapper); @@ -120,8 +114,7 @@ public void addNewCredentialConfig_Success() { when(credentialConfigMapper.toEntity(any(CredentialConfigurationDTO.class))).thenReturn(credentialConfig); when(credentialConfigRepository.save(any(CredentialConfig.class))).thenReturn(credentialConfig); - CredentialConfigResponse credentialConfigResponse = credentialConfigurationService - .addCredentialConfiguration(credentialConfigurationDTO); + CredentialConfigResponse credentialConfigResponse = credentialConfigurationService.addCredentialConfiguration(credentialConfigurationDTO); Assert.assertNotNull(credentialConfigResponse); Assert.assertNotNull(credentialConfigResponse.getId()); @@ -138,10 +131,8 @@ public void addCredentialConfiguration_DataProviderMode_VcTemplateNull_ThrowsExc dto.setVcTemplate(null); // or "" // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, - () -> credentialConfigurationService.addCredentialConfiguration(dto)); - org.junit.Assert.assertEquals("Credential Template is mandatory for the DataProvider plugin issuer.", - exception.getMessage()); + CertifyException exception = assertThrows(CertifyException.class, () -> credentialConfigurationService.addCredentialConfiguration(dto)); + org.junit.Assert.assertEquals("Credential Template is mandatory for the DataProvider plugin issuer.", exception.getMessage()); } @Test @@ -149,8 +140,7 @@ public void getCredentialConfigById_Success() { Optional optional = Optional.of(credentialConfig); when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(optional); when(credentialConfigMapper.toDto(any(CredentialConfig.class))).thenReturn(credentialConfigurationDTO); - CredentialConfigurationDTO credentialConfigurationDTOResponse = credentialConfigurationService - .getCredentialConfigurationById("test"); + CredentialConfigurationDTO credentialConfigurationDTOResponse = credentialConfigurationService.getCredentialConfigurationById("test"); Assert.assertNotNull(credentialConfigurationDTOResponse); Assert.assertNotNull(credentialConfigurationDTOResponse.getCredentialTypes()); @@ -158,20 +148,16 @@ public void getCredentialConfigById_Success() { Assert.assertNotNull(credentialConfigurationDTOResponse.getContextURLs()); Assert.assertNotNull(credentialConfigurationDTOResponse.getVcTemplate()); Assert.assertEquals("test_template", credentialConfigurationDTOResponse.getVcTemplate()); - Assert.assertEquals(List.of("https://www.w3.org/2018/credentials/v1"), - credentialConfigurationDTOResponse.getContextURLs()); - Assert.assertEquals(Arrays.asList("VerifiableCredential", "TestVerifiableCredential"), - credentialConfigurationDTOResponse.getCredentialTypes()); + Assert.assertEquals(List.of("https://www.w3.org/2018/credentials/v1"), credentialConfigurationDTOResponse.getContextURLs()); + Assert.assertEquals(Arrays.asList("VerifiableCredential", "TestVerifiableCredential"), credentialConfigurationDTOResponse.getCredentialTypes()); Assert.assertEquals("ldp_vc", credentialConfigurationDTOResponse.getCredentialFormat()); } @Test public void getCredentialConfigurationById_ConfigNotFound() { - when(credentialConfigRepository.findByCredentialConfigKeyId("12345678")) - .thenReturn(Optional.empty()); + when(credentialConfigRepository.findByCredentialConfigKeyId("12345678")).thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, - () -> credentialConfigurationService.getCredentialConfigurationById("12345678")); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> credentialConfigurationService.getCredentialConfigurationById("12345678")); assertEquals("Configuration not found with the provided id: " + "12345678", exception.getMessage()); } @@ -180,10 +166,8 @@ public void getCredentialConfigurationById_ConfigNotFound() { public void getCredentialConfigurationById_ConfigNotActive_ThrowsException() { CredentialConfig inactiveConfig = new CredentialConfig(); inactiveConfig.setStatus("inactive"); // Not Constants.ACTIVE - when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())) - .thenReturn(Optional.of(inactiveConfig)); - CertifyException exception = assertThrows(CertifyException.class, - () -> credentialConfigurationService.getCredentialConfigurationById("test-id")); + when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(Optional.of(inactiveConfig)); + CertifyException exception = assertThrows(CertifyException.class, () -> credentialConfigurationService.getCredentialConfigurationById("test-id")); assertEquals("Configuration not active.", exception.getMessage()); } @@ -216,13 +200,11 @@ public void updateExistingCredentialConfig_Success() { // Mock the mapper methods when(credentialConfigMapper.toDto(any(CredentialConfig.class))).thenReturn(validationDto); - doNothing().when(credentialConfigMapper).updateEntityFromDto(any(CredentialConfigurationDTO.class), - any(CredentialConfig.class)); + doNothing().when(credentialConfigMapper).updateEntityFromDto(any(CredentialConfigurationDTO.class), any(CredentialConfig.class)); when(credentialConfigRepository.save(any(CredentialConfig.class))).thenReturn(mockCredentialConfig); // --- Act --- - CredentialConfigResponse credentialConfigResponse = credentialConfigurationService - .updateCredentialConfiguration(expectedId, mockDto); + CredentialConfigResponse credentialConfigResponse = credentialConfigurationService.updateCredentialConfiguration(expectedId, mockDto); // --- Assert --- Assert.assertNotNull(credentialConfigResponse); @@ -240,12 +222,9 @@ public void updateExistingCredentialConfig_Success() { @Test public void updateExistingCredentialConfiguration_ConfigNotFound() { - when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())) - .thenReturn(Optional.empty()); + when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, - () -> credentialConfigurationService.updateCredentialConfiguration("12345678", - new CredentialConfigurationDTO())); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> credentialConfigurationService.updateCredentialConfiguration("12345678", new CredentialConfigurationDTO())); assertEquals("Configuration not found with the provided id: " + "12345678", exception.getMessage()); } @@ -263,11 +242,9 @@ public void deleteCredentialConfig_Success() { @Test public void deleteCredentialConfiguration_ConfigNotFound() { - when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())) - .thenReturn(Optional.empty()); + when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, - () -> credentialConfigurationService.deleteCredentialConfigurationById("12345678")); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> credentialConfigurationService.deleteCredentialConfigurationById("12345678")); assertEquals("Configuration not found with the provided id: 12345678", exception.getMessage()); } @@ -318,8 +295,7 @@ public void fetchCredentialIssuerMetadata_SigningAlgValuesSupported_UsesSignatur Assert.assertNotNull(result); Assert.assertTrue(result.getCredentialConfigurationSupportedDTO().containsKey("test-credential")); - CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO() - .get("test-credential"); + CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO().get("test-credential"); Assert.assertEquals(List.of("ES256"), supportedDTO.getCredentialSigningAlgValuesSupported()); } @@ -343,8 +319,7 @@ public void fetchCredentialIssuerMetadata_SigningAlgValuesSupported_UsesSignatur Assert.assertNotNull(result); Assert.assertTrue(result.getCredentialConfigurationSupportedDTO().containsKey("sdjwt-credential")); - CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO() - .get("sdjwt-credential"); + CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO().get("sdjwt-credential"); Assert.assertEquals(List.of("ES256"), supportedDTO.getCredentialSigningAlgValuesSupported()); } @@ -384,8 +359,7 @@ public void fetchCredentialIssuerMetadata_invalidVersion() { // Call with specific version - CertifyException ex = assertThrows(CertifyException.class, - () -> credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported_version")); + CertifyException ex = assertThrows(CertifyException.class, () -> credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported_version")); assertEquals("Unsupported version: unsupported_version", ex.getMessage()); } @@ -420,8 +394,7 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { mdocConfig.setStatus("active"); mdocConfig.setCredentialFormat("mso_mdoc"); - mdocConfig.setMsoMdocClaims(Map.of("firstName", Map.of("First Name", - new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test", "en")))))); + mdocConfig.setMsoMdocClaims(Map.of("firstName", Map.of("First Name", new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test", "en")))))); mdocConfig.setDocType("docType1"); List credentialConfigList = List.of(mdocConfig); @@ -432,8 +405,7 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { mdocDTO.setCredentialFormat("mso_mdoc"); mdocDTO.setCredentialConfigKeyId("mdoc-credential"); mdocDTO.setScope("mdoc_scope"); - mdocDTO.setMsoMdocClaims(Map.of("firstName", Map.of("First Name", - new ClaimsDisplayFieldsConfigDTO(List.of(new ClaimsDisplayFieldsConfigDTO.Display("Test", "en")))))); + mdocDTO.setMsoMdocClaims(Map.of("firstName", Map.of("First Name", new ClaimsDisplayFieldsConfigDTO(List.of(new ClaimsDisplayFieldsConfigDTO.Display("Test", "en")))))); mdocDTO.setDocType("docType1"); when(credentialConfigMapper.toDto(mdocConfig)).thenReturn(mdocDTO); @@ -444,15 +416,9 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { // Verify MSO_MDOC configuration Assert.assertNotNull(result.getCredentialConfigurationSupportedDTO()); Assert.assertEquals(1, result.getCredentialConfigurationSupportedDTO().size()); - Assert.assertEquals( - Map.of("firstName", - Map.of("First Name", - new ClaimsDisplayFieldsConfigs( - List.of(new ClaimsDisplayFieldsConfigs.Display("Test", "en"))))), - result.getCredentialConfigurationSupportedDTO().get("mdoc-credential").getClaims()); - - CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO() - .get("mdoc-credential"); + Assert.assertEquals(Map.of("firstName", Map.of("First Name", new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test", "en"))))), result.getCredentialConfigurationSupportedDTO().get("mdoc-credential").getClaims()); + + CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO().get("mdoc-credential"); Assert.assertNotNull(supportedDTO); Assert.assertEquals("mso_mdoc", supportedDTO.getFormat()); Assert.assertNotNull(supportedDTO.getClaims()); @@ -525,10 +491,8 @@ public void validateCredentialConfiguration_LdpVc_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); - assertEquals("Context, credentialType and signatureCryptoSuite are mandatory for ldp_vc format", - ex.getMessage()); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + assertEquals("Context, credentialType and signatureCryptoSuite are mandatory for ldp_vc format", ex.getMessage()); } } @@ -540,8 +504,7 @@ public void validateCredentialConfiguration_LdpVc_Duplicate_ThrowsException() { try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> LdpVcCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Configuration already exists for the given context and credentialType", ex.getMessage()); } } @@ -553,8 +516,7 @@ public void validateCredentialConfiguration_MsoMdoc_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(MsoMdocCredentialConfigValidator.class)) { mocked.when(() -> MsoMdocCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Doctype and signatureCryptoSuite fields are mandatory for mso_mdoc format", ex.getMessage()); } } @@ -567,8 +529,7 @@ public void validateCredentialConfiguration_MsoMdoc_Duplicate_ThrowsException() try (var mocked = org.mockito.Mockito.mockStatic(MsoMdocCredentialConfigValidator.class)) { mocked.when(() -> MsoMdocCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> MsoMdocCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Configuration already exists for the given doctype", ex.getMessage()); } } @@ -580,8 +541,7 @@ public void validateCredentialConfiguration_SdJwt_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(SdJwtCredentialConfigValidator.class)) { mocked.when(() -> SdJwtCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Vct and signatureAlgo fields are mandatory for vc+sd-jwt format", ex.getMessage()); } } @@ -594,8 +554,7 @@ public void validateCredentialConfiguration_SdJwt_Duplicate_ThrowsException() { try (var mocked = org.mockito.Mockito.mockStatic(SdJwtCredentialConfigValidator.class)) { mocked.when(() -> SdJwtCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> SdJwtCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Configuration already exists for the given vct", ex.getMessage()); } } @@ -610,8 +569,7 @@ public void validateCredentialConfiguration_LdpVc_MissingSignatureAlgo_ThrowsExc try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> LdpVcCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Unsupported signature crypto suite: test-rdfc-2019", ex.getMessage()); } } @@ -622,10 +580,8 @@ public void validateCredentialConfiguration_MultipleCredentialStatusPurposes_Thr dto.setCredentialFormat("ldp_vc"); dto.setVcTemplate("test_template"); dto.setCredentialStatusPurposes(List.of("purpose1", "purpose2")); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", - List.of("purpose1")); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1")); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Multiple credential status purposes are not currently supported.", ex.getMessage()); } @@ -635,10 +591,8 @@ public void validateCredentialConfiguration_InvalidCredentialStatusPurpose_Throw dto.setCredentialFormat("ldp_vc"); dto.setVcTemplate("test_template"); dto.setCredentialStatusPurposes(List.of("invalid_purpose")); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", - List.of("purpose1", "purpose2")); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils - .invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); + CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); assertEquals("Invalid credential status purposes. Allowed values are: [purpose1, purpose2]", ex.getMessage()); } @@ -654,12 +608,10 @@ public void validateCredentialConfiguration_NullCredentialStatusPurposes_AllowsC dto.setSignatureAlgo("EdDSA"); dto.setKeyManagerAppId("TEST2019"); dto.setKeyManagerRefId("TEST2019-REF"); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", - List.of("purpose1", "purpose2")); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, - true); + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true); } } @@ -675,12 +627,10 @@ public void validateCredentialConfiguration_EmptyCredentialStatusPurposes_Allows dto.setSignatureAlgo("EdDSA"); dto.setKeyManagerAppId("TEST2019"); dto.setKeyManagerRefId("TEST2019-REF"); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", - List.of("purpose1", "purpose2")); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, - true); + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true); } } @@ -696,12 +646,10 @@ public void validateCredentialConfiguration_ValidSingleCredentialStatusPurpose_S dto.setSignatureAlgo("EdDSA"); dto.setKeyManagerAppId("TEST2019"); dto.setKeyManagerRefId("TEST2019-REF"); - ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", - List.of("purpose1", "purpose2")); + ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); - ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, - true); + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true); } } @@ -716,10 +664,8 @@ public void validateKeyAliasMapperConfiguration_KeyAliasListIsNull_ThrowsExcepti dto.setVcTemplate("test_template"); // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, - () -> credentialConfigurationService.addCredentialConfiguration(dto)); - assertEquals("No key chooser configuration found for the signatureAlgo: RsaSignature2018", - exception.getMessage()); + CertifyException exception = assertThrows(CertifyException.class, () -> credentialConfigurationService.addCredentialConfiguration(dto)); + assertEquals("No key chooser configuration found for the signatureAlgo: RsaSignature2018", exception.getMessage()); } @Test @@ -735,8 +681,7 @@ public void validateKeyAliasMpperConfiguration_NoMatchingAppIdAndRefId_ThrowsExc dto.setVcTemplate("test_template"); // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, - () -> credentialConfigurationService.addCredentialConfiguration(dto)); + CertifyException exception = assertThrows(CertifyException.class, () -> credentialConfigurationService.addCredentialConfiguration(dto)); assertEquals("No matching appId and refId found in the key chooser list.", exception.getMessage()); } } From d5fce4088aa9fbac5fee377eb292eb1bd3ef2a7d Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Wed, 17 Dec 2025 14:18:22 +0530 Subject: [PATCH 08/13] [INJICERT-1239] feat(tests): improve readability and consistency in CredentialConfigurationServiceImplTest Signed-off-by: amaydixit11 --- ...redentialConfigurationServiceImplTest.java | 91 +++++++++++++------ 1 file changed, 64 insertions(+), 27 deletions(-) diff --git a/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java b/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java index 79e9161f..790d3a69 100644 --- a/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java +++ b/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java @@ -54,7 +54,7 @@ public class CredentialConfigurationServiceImplTest { public void setup() { Map>> keyAliasMapper = new HashMap<>(); keyAliasMapper.put("EdDSA", List.of(List.of("TEST2019", "TEST2019-REF"))); - // keyAliasMapper.put("RS256", List.of()); +// keyAliasMapper.put("RS256", List.of()); MockitoAnnotations.openMocks(this); credentialConfig = new CredentialConfig(); @@ -101,7 +101,6 @@ public void setup() { ReflectionTestUtils.setField(credentialConfigurationService, "proofTypesSupported", new LinkedHashMap<>()); ReflectionTestUtils.setField(credentialConfigurationService, "keyAliasMapper", keyAliasMapper); - // Mock authServerService to return the expected authorization server URLs when(authServerService.getAllAuthorizationServerUrls()).thenReturn(List.of("http://auth.com")); } @@ -131,7 +130,9 @@ public void addCredentialConfiguration_DataProviderMode_VcTemplateNull_ThrowsExc dto.setVcTemplate(null); // or "" // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, () -> credentialConfigurationService.addCredentialConfiguration(dto)); + CertifyException exception = assertThrows(CertifyException.class, () -> + credentialConfigurationService.addCredentialConfiguration(dto) + ); org.junit.Assert.assertEquals("Credential Template is mandatory for the DataProvider plugin issuer.", exception.getMessage()); } @@ -155,9 +156,11 @@ public void getCredentialConfigById_Success() { @Test public void getCredentialConfigurationById_ConfigNotFound() { - when(credentialConfigRepository.findByCredentialConfigKeyId("12345678")).thenReturn(Optional.empty()); + when(credentialConfigRepository.findByCredentialConfigKeyId("12345678")) + .thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> credentialConfigurationService.getCredentialConfigurationById("12345678")); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> + credentialConfigurationService.getCredentialConfigurationById("12345678")); assertEquals("Configuration not found with the provided id: " + "12345678", exception.getMessage()); } @@ -167,7 +170,9 @@ public void getCredentialConfigurationById_ConfigNotActive_ThrowsException() { CredentialConfig inactiveConfig = new CredentialConfig(); inactiveConfig.setStatus("inactive"); // Not Constants.ACTIVE when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(Optional.of(inactiveConfig)); - CertifyException exception = assertThrows(CertifyException.class, () -> credentialConfigurationService.getCredentialConfigurationById("test-id")); + CertifyException exception = assertThrows(CertifyException.class, () -> + credentialConfigurationService.getCredentialConfigurationById("test-id") + ); assertEquals("Configuration not active.", exception.getMessage()); } @@ -184,9 +189,11 @@ public void updateExistingCredentialConfig_Success() { mockCredentialConfig.setSdJwtVct("test-vct"); mockCredentialConfig.setSignatureAlgo("ES256"); + Optional optionalConfig = Optional.of(mockCredentialConfig); when(credentialConfigRepository.findByCredentialConfigKeyId(eq(expectedId))).thenReturn(optionalConfig); + CredentialConfigurationDTO mockDto = new CredentialConfigurationDTO(); // Create a valid DTO for validation that will be returned by toDto @@ -222,9 +229,11 @@ public void updateExistingCredentialConfig_Success() { @Test public void updateExistingCredentialConfiguration_ConfigNotFound() { - when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(Optional.empty()); + when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())) + .thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> credentialConfigurationService.updateCredentialConfiguration("12345678", new CredentialConfigurationDTO())); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> + credentialConfigurationService.updateCredentialConfiguration("12345678", new CredentialConfigurationDTO())); assertEquals("Configuration not found with the provided id: " + "12345678", exception.getMessage()); } @@ -242,9 +251,11 @@ public void deleteCredentialConfig_Success() { @Test public void deleteCredentialConfiguration_ConfigNotFound() { - when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())).thenReturn(Optional.empty()); + when(credentialConfigRepository.findByCredentialConfigKeyId(anyString())) + .thenReturn(Optional.empty()); - CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> credentialConfigurationService.deleteCredentialConfigurationById("12345678")); + CredentialConfigException exception = assertThrows(CredentialConfigException.class, () -> + credentialConfigurationService.deleteCredentialConfigurationById("12345678")); assertEquals("Configuration not found with the provided id: 12345678", exception.getMessage()); } @@ -315,6 +326,7 @@ public void fetchCredentialIssuerMetadata_SigningAlgValuesSupported_UsesSignatur dto.setCredentialFormat("vc+sd-jwt"); when(credentialConfigMapper.toDto(config)).thenReturn(dto); + CredentialIssuerMetadataDTO result = credentialConfigurationService.fetchCredentialIssuerMetadata("latest"); Assert.assertNotNull(result); @@ -359,7 +371,9 @@ public void fetchCredentialIssuerMetadata_invalidVersion() { // Call with specific version - CertifyException ex = assertThrows(CertifyException.class, () -> credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported_version")); + CertifyException ex = assertThrows(CertifyException.class, () -> + credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported_version") + ); assertEquals("Unsupported version: unsupported_version", ex.getMessage()); } @@ -394,7 +408,7 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { mdocConfig.setStatus("active"); mdocConfig.setCredentialFormat("mso_mdoc"); - mdocConfig.setMsoMdocClaims(Map.of("firstName", Map.of("First Name", new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test", "en")))))); + mdocConfig.setMsoMdocClaims(Map.of("firstName", Map.of( "First Name", new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test","en")))))); mdocConfig.setDocType("docType1"); List credentialConfigList = List.of(mdocConfig); @@ -405,7 +419,7 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { mdocDTO.setCredentialFormat("mso_mdoc"); mdocDTO.setCredentialConfigKeyId("mdoc-credential"); mdocDTO.setScope("mdoc_scope"); - mdocDTO.setMsoMdocClaims(Map.of("firstName", Map.of("First Name", new ClaimsDisplayFieldsConfigDTO(List.of(new ClaimsDisplayFieldsConfigDTO.Display("Test", "en")))))); + mdocDTO.setMsoMdocClaims(Map.of("firstName", Map.of( "First Name", new ClaimsDisplayFieldsConfigDTO(List.of(new ClaimsDisplayFieldsConfigDTO.Display("Test","en")))))); mdocDTO.setDocType("docType1"); when(credentialConfigMapper.toDto(mdocConfig)).thenReturn(mdocDTO); @@ -416,7 +430,7 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { // Verify MSO_MDOC configuration Assert.assertNotNull(result.getCredentialConfigurationSupportedDTO()); Assert.assertEquals(1, result.getCredentialConfigurationSupportedDTO().size()); - Assert.assertEquals(Map.of("firstName", Map.of("First Name", new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test", "en"))))), result.getCredentialConfigurationSupportedDTO().get("mdoc-credential").getClaims()); + Assert.assertEquals(Map.of("firstName", Map.of( "First Name", new ClaimsDisplayFieldsConfigs(List.of(new ClaimsDisplayFieldsConfigs.Display("Test","en"))))), result.getCredentialConfigurationSupportedDTO().get("mdoc-credential").getClaims()); CredentialConfigurationSupportedDTO supportedDTO = result.getCredentialConfigurationSupportedDTO().get("mdoc-credential"); Assert.assertNotNull(supportedDTO); @@ -429,7 +443,7 @@ public void fetchCredentialIssuerMetadata_MsoMdocFormat() { // Add these methods to CredentialConfigurationServiceImplTest @Test - public void addNewCredentialConfig_MsoMdoc_Success() { + public void addNewCredentialConfig_MsoMdoc_Success(){ CredentialConfig mdocConfig = new CredentialConfig(); mdocConfig.setConfigId(UUID.randomUUID().toString()); mdocConfig.setCredentialConfigKeyId("mdoc-credential"); @@ -477,6 +491,7 @@ public void addNewCredentialConfig_SdJwt_Success() { when(credentialConfigMapper.toEntity(any(CredentialConfigurationDTO.class))).thenReturn(sdJwtConfig); when(credentialConfigRepository.save(any(CredentialConfig.class))).thenReturn(sdJwtConfig); + CredentialConfigResponse response = credentialConfigurationService.addCredentialConfiguration(sdJwtDTO); Assert.assertNotNull(response); @@ -491,7 +506,9 @@ public void validateCredentialConfiguration_LdpVc_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Context, credentialType and signatureCryptoSuite are mandatory for ldp_vc format", ex.getMessage()); } } @@ -504,7 +521,9 @@ public void validateCredentialConfiguration_LdpVc_Duplicate_ThrowsException() { try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> LdpVcCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Configuration already exists for the given context and credentialType", ex.getMessage()); } } @@ -516,7 +535,9 @@ public void validateCredentialConfiguration_MsoMdoc_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(MsoMdocCredentialConfigValidator.class)) { mocked.when(() -> MsoMdocCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Doctype and signatureCryptoSuite fields are mandatory for mso_mdoc format", ex.getMessage()); } } @@ -529,7 +550,9 @@ public void validateCredentialConfiguration_MsoMdoc_Duplicate_ThrowsException() try (var mocked = org.mockito.Mockito.mockStatic(MsoMdocCredentialConfigValidator.class)) { mocked.when(() -> MsoMdocCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> MsoMdocCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Configuration already exists for the given doctype", ex.getMessage()); } } @@ -541,7 +564,9 @@ public void validateCredentialConfiguration_SdJwt_Invalid_ThrowsException() { dto.setVcTemplate("test_template"); try (var mocked = org.mockito.Mockito.mockStatic(SdJwtCredentialConfigValidator.class)) { mocked.when(() -> SdJwtCredentialConfigValidator.isValidCheck(dto)).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Vct and signatureAlgo fields are mandatory for vc+sd-jwt format", ex.getMessage()); } } @@ -554,7 +579,9 @@ public void validateCredentialConfiguration_SdJwt_Duplicate_ThrowsException() { try (var mocked = org.mockito.Mockito.mockStatic(SdJwtCredentialConfigValidator.class)) { mocked.when(() -> SdJwtCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> SdJwtCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(true); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Configuration already exists for the given vct", ex.getMessage()); } } @@ -569,7 +596,9 @@ public void validateCredentialConfiguration_LdpVc_MissingSignatureAlgo_ThrowsExc try (var mocked = org.mockito.Mockito.mockStatic(LdpVcCredentialConfigValidator.class)) { mocked.when(() -> LdpVcCredentialConfigValidator.isValidCheck(dto)).thenReturn(true); mocked.when(() -> LdpVcCredentialConfigValidator.isConfigAlreadyPresent(eq(dto), any())).thenReturn(false); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Unsupported signature crypto suite: test-rdfc-2019", ex.getMessage()); } } @@ -581,7 +610,9 @@ public void validateCredentialConfiguration_MultipleCredentialStatusPurposes_Thr dto.setVcTemplate("test_template"); dto.setCredentialStatusPurposes(List.of("purpose1", "purpose2")); ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1")); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Multiple credential status purposes are not currently supported.", ex.getMessage()); } @@ -592,7 +623,9 @@ public void validateCredentialConfiguration_InvalidCredentialStatusPurpose_Throw dto.setVcTemplate("test_template"); dto.setCredentialStatusPurposes(List.of("invalid_purpose")); ReflectionTestUtils.setField(credentialConfigurationService, "allowedCredentialStatusPurposes", List.of("purpose1", "purpose2")); - CertifyException ex = assertThrows(CertifyException.class, () -> ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true)); + CertifyException ex = assertThrows(CertifyException.class, () -> + ReflectionTestUtils.invokeMethod(credentialConfigurationService, "validateCredentialConfiguration", dto, true) + ); assertEquals("Invalid credential status purposes. Allowed values are: [purpose1, purpose2]", ex.getMessage()); } @@ -664,7 +697,9 @@ public void validateKeyAliasMapperConfiguration_KeyAliasListIsNull_ThrowsExcepti dto.setVcTemplate("test_template"); // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, () -> credentialConfigurationService.addCredentialConfiguration(dto)); + CertifyException exception = assertThrows(CertifyException.class, () -> + credentialConfigurationService.addCredentialConfiguration(dto) + ); assertEquals("No key chooser configuration found for the signatureAlgo: RsaSignature2018", exception.getMessage()); } @@ -681,7 +716,9 @@ public void validateKeyAliasMpperConfiguration_NoMatchingAppIdAndRefId_ThrowsExc dto.setVcTemplate("test_template"); // Act & Assert - CertifyException exception = assertThrows(CertifyException.class, () -> credentialConfigurationService.addCredentialConfiguration(dto)); + CertifyException exception = assertThrows(CertifyException.class, () -> + credentialConfigurationService.addCredentialConfiguration(dto) + ); assertEquals("No matching appId and refId found in the key chooser list.", exception.getMessage()); } -} +} \ No newline at end of file From 9cd2e03eed8d5286bbac20cb5426bfa960fe2994 Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Wed, 17 Dec 2025 14:20:44 +0530 Subject: [PATCH 09/13] [INJICERT-1239] feat(tests): improve readability and consistency in CredentialConfigurationServiceImplTest Signed-off-by: amaydixit11 --- .../services/CredentialConfigurationServiceImplTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java b/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java index 790d3a69..18ad6945 100644 --- a/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java +++ b/certify-service/src/test/java/io/mosip/certify/services/CredentialConfigurationServiceImplTest.java @@ -53,7 +53,8 @@ public class CredentialConfigurationServiceImplTest { @Before public void setup() { Map>> keyAliasMapper = new HashMap<>(); - keyAliasMapper.put("EdDSA", List.of(List.of("TEST2019", "TEST2019-REF"))); + keyAliasMapper.put("EdDSA", List.of( + List.of("TEST2019", "TEST2019-REF"))); // keyAliasMapper.put("RS256", List.of()); MockitoAnnotations.openMocks(this); @@ -302,6 +303,7 @@ public void fetchCredentialIssuerMetadata_SigningAlgValuesSupported_UsesSignatur dto.setCredentialFormat("ldp_vc"); when(credentialConfigMapper.toDto(config)).thenReturn(dto); + CredentialIssuerMetadataDTO result = credentialConfigurationService.fetchCredentialIssuerMetadata("latest"); Assert.assertNotNull(result); @@ -326,7 +328,6 @@ public void fetchCredentialIssuerMetadata_SigningAlgValuesSupported_UsesSignatur dto.setCredentialFormat("vc+sd-jwt"); when(credentialConfigMapper.toDto(config)).thenReturn(dto); - CredentialIssuerMetadataDTO result = credentialConfigurationService.fetchCredentialIssuerMetadata("latest"); Assert.assertNotNull(result); From 0c981d2ef00a0a634ac3fb115b9fd78206ac0e24 Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Fri, 19 Dec 2025 00:36:23 +0530 Subject: [PATCH 10/13] [INJICERT-1239] fix(ExceptionHandlerAdvice): update response status codes for error handling Signed-off-by: amaydixit11 --- .../mosip/certify/advice/ExceptionHandlerAdvice.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/certify-service/src/main/java/io/mosip/certify/advice/ExceptionHandlerAdvice.java b/certify-service/src/main/java/io/mosip/certify/advice/ExceptionHandlerAdvice.java index f2ad606c..5d7a673e 100644 --- a/certify-service/src/main/java/io/mosip/certify/advice/ExceptionHandlerAdvice.java +++ b/certify-service/src/main/java/io/mosip/certify/advice/ExceptionHandlerAdvice.java @@ -118,15 +118,16 @@ private ResponseEntity handleInternalControllerException(Except } if(ex instanceof MissingServletRequestParameterException) { return new ResponseEntity(getResponseWrapper(INVALID_REQUEST, ex.getMessage()), - HttpStatus.OK); + HttpStatus.BAD_REQUEST); } if(ex instanceof HttpMediaTypeNotAcceptableException) { return new ResponseEntity(getResponseWrapper(INVALID_REQUEST, ex.getMessage()), - HttpStatus.OK); + HttpStatus.NOT_ACCEPTABLE); } if(ex instanceof CertifyException) { String errorCode = ((CertifyException) ex).getErrorCode(); - return new ResponseEntity(getResponseWrapper(errorCode, getMessage(errorCode)), HttpStatus.OK); + return new ResponseEntity(getResponseWrapper(errorCode, getMessage(errorCode)), + HttpStatus.BAD_REQUEST); } if(ex instanceof RenderingTemplateException) { return new ResponseEntity<>(getResponseWrapper(INVALID_REQUEST, ex.getMessage()) ,HttpStatus.NOT_FOUND); @@ -142,7 +143,8 @@ private ResponseEntity handleInternalControllerException(Except return new ResponseEntity(getResponseWrapper(HttpStatus.FORBIDDEN.name(), HttpStatus.FORBIDDEN.getReasonPhrase()), HttpStatus.FORBIDDEN); } - return new ResponseEntity(getResponseWrapper(UNKNOWN_ERROR, ex.getMessage()), HttpStatus.OK); + return new ResponseEntity(getResponseWrapper(UNKNOWN_ERROR, ex.getMessage()), + HttpStatus.INTERNAL_SERVER_ERROR); } public ResponseEntity handleVCIControllerExceptions(Exception ex) { From 66c97309eb407dcb87167ce6a8e636bb616623b8 Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Fri, 19 Dec 2025 01:09:32 +0530 Subject: [PATCH 11/13] [INJICERT-1239] fix(PreAuthorizedCodeService): enhance claims validation logic for credential configurations Signed-off-by: amaydixit11 --- .../services/PreAuthorizedCodeService.java | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/certify-service/src/main/java/io/mosip/certify/services/PreAuthorizedCodeService.java b/certify-service/src/main/java/io/mosip/certify/services/PreAuthorizedCodeService.java index ed8f9fec..532da077 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/PreAuthorizedCodeService.java +++ b/certify-service/src/main/java/io/mosip/certify/services/PreAuthorizedCodeService.java @@ -98,20 +98,47 @@ private void validatePreAuthorizedRequest(PreAuthorizedRequest request) { } CredentialConfigurationSupportedDTO config = supportedConfigs.get(request.getCredentialConfigurationId()); - Map requiredClaims = config.getClaims(); - - validateClaims(requiredClaims, request.getClaims()); + validateClaims(config, request.getClaims()); } - private void validateClaims(Map requiredClaims, Map providedClaims) { - if (requiredClaims == null || requiredClaims.isEmpty()) { - return; - } - + private void validateClaims(CredentialConfigurationSupportedDTO config, Map providedClaims) { if (providedClaims == null) { providedClaims = Collections.emptyMap(); } + String format = config.getFormat(); + Set allowedClaimKeys; + + if ("ldp_vc".equals(format)) { + // For ldp_vc: claims are defined in credential_definition.credentialSubject + CredentialDefinition credDef = config.getCredentialDefinition(); + if (credDef != null && credDef.getCredentialSubject() != null) { + allowedClaimKeys = credDef.getCredentialSubject().keySet(); + } else { + return; // No claims defined, allow any + } + // For ldp_vc, just validate unknown claims (mandatory not supported in this structure) + List unknownClaims = new ArrayList<>(); + for (String providedClaim : providedClaims.keySet()) { + if (!allowedClaimKeys.contains(providedClaim)) { + unknownClaims.add(providedClaim); + } + } + if (!unknownClaims.isEmpty()) { + log.error("Unknown claims provided: {}", unknownClaims); + throw new InvalidRequestException(ErrorConstants.UNKNOWN_CLAIMS); + } + } else { + // For mso_mdoc, vc+sd-jwt: use top-level claims with mandatory checking + Map requiredClaims = config.getClaims(); + if (requiredClaims == null || requiredClaims.isEmpty()) { + return; + } + validateClaimsWithMandatory(requiredClaims, providedClaims); + } + } + + private void validateClaimsWithMandatory(Map requiredClaims, Map providedClaims) { List missingClaims = new ArrayList<>(); List unknownClaims = new ArrayList<>(); From 43990da157a47dd0db3c974632bb3eea6e969b50 Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Fri, 19 Dec 2025 01:41:53 +0530 Subject: [PATCH 12/13] [INJICERT-1239] test(AuthorizationServerService): add unit tests for authorization server initialization and metadata discovery Signed-off-by: amaydixit11 --- .../AuthorizationServerServiceTest.java | 547 ++++++++++++++++++ 1 file changed, 547 insertions(+) create mode 100644 certify-service/src/test/java/io/mosip/certify/services/AuthorizationServerServiceTest.java diff --git a/certify-service/src/test/java/io/mosip/certify/services/AuthorizationServerServiceTest.java b/certify-service/src/test/java/io/mosip/certify/services/AuthorizationServerServiceTest.java new file mode 100644 index 00000000..1d8349b2 --- /dev/null +++ b/certify-service/src/test/java/io/mosip/certify/services/AuthorizationServerServiceTest.java @@ -0,0 +1,547 @@ +package io.mosip.certify.services; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.mosip.certify.core.constants.Constants; +import io.mosip.certify.core.constants.ErrorConstants; +import io.mosip.certify.core.dto.AuthorizationServerConfig; +import io.mosip.certify.core.dto.AuthorizationServerMetadata; +import io.mosip.certify.core.exception.CertifyException; +import io.mosip.certify.core.exception.InvalidRequestException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class AuthorizationServerServiceTest { + + @Mock + private VCICacheService vciCacheService; + + @Mock + private ObjectMapper objectMapper; + + @Mock + private RestTemplate restTemplate; + + @InjectMocks + private AuthorizationServerService authorizationServerService; + + private static final String INTERNAL_SERVER_URL = "https://internal-auth.example.com"; + private static final String EXTERNAL_SERVER_URL = "https://external-auth.example.com"; + private static final String DEFAULT_SERVER_URL = "https://default-auth.example.com"; + + @Before + public void setup() { + ReflectionTestUtils.setField(authorizationServerService, "retryCount", 3); + ReflectionTestUtils.setField(authorizationServerService, "internalAuthServerUrl", INTERNAL_SERVER_URL); + ReflectionTestUtils.setField(authorizationServerService, "authorizationServersConfig", ""); + ReflectionTestUtils.setField(authorizationServerService, "defaultAuthServer", ""); + ReflectionTestUtils.setField(authorizationServerService, "credentialConfigMappingJson", "{}"); + } + + // ========== Tests for initialize() and loadConfiguredServers() ========== + + @Test + public void initialize_WithInternalServerOnly_Success() { + authorizationServerService.initialize(); + + List urls = authorizationServerService.getAllAuthorizationServerUrls(); + assertEquals(1, urls.size()); + assertEquals(INTERNAL_SERVER_URL, urls.get(0)); + } + + @Test + public void initialize_WithExternalServers_Success() { + ReflectionTestUtils.setField(authorizationServerService, "authorizationServersConfig", + "https://auth1.example.com, https://auth2.example.com"); + + authorizationServerService.initialize(); + + List urls = authorizationServerService.getAllAuthorizationServerUrls(); + assertEquals(3, urls.size()); // internal + 2 external + } + + @Test + public void initialize_WithNoServers_NoException() { + ReflectionTestUtils.setField(authorizationServerService, "internalAuthServerUrl", ""); + ReflectionTestUtils.setField(authorizationServerService, "authorizationServersConfig", ""); + + authorizationServerService.initialize(); + + List urls = authorizationServerService.getAllAuthorizationServerUrls(); + assertEquals(0, urls.size()); + } + + @Test + public void initialize_WithEmptyExternalConfig_OnlyInternalAdded() { + ReflectionTestUtils.setField(authorizationServerService, "authorizationServersConfig", " , "); + + authorizationServerService.initialize(); + + List urls = authorizationServerService.getAllAuthorizationServerUrls(); + assertEquals(1, urls.size()); + assertEquals(INTERNAL_SERVER_URL, urls.get(0)); + } + + // ========== Tests for loadCredentialConfigMappings() ========== + + @Test + public void initialize_WithCredentialConfigMappings_Success() throws Exception { + String mappingJson = "{\"config1\":\"https://auth1.example.com\",\"config2\":\"https://auth2.example.com\"}"; + ReflectionTestUtils.setField(authorizationServerService, "credentialConfigMappingJson", mappingJson); + + when(objectMapper.readValue(eq(mappingJson), any(TypeReference.class))) + .thenReturn(Map.of("config1", "https://auth1.example.com", "config2", "https://auth2.example.com")); + + authorizationServerService.initialize(); + + verify(objectMapper).readValue(eq(mappingJson), any(TypeReference.class)); + } + + @Test + public void initialize_WithInvalidCredentialConfigMappings_NoException() throws Exception { + String mappingJson = "invalid-json"; + ReflectionTestUtils.setField(authorizationServerService, "credentialConfigMappingJson", mappingJson); + + when(objectMapper.readValue(eq(mappingJson), any(TypeReference.class))) + .thenThrow(new RuntimeException("Invalid JSON")); + + // Should not throw, just log error + authorizationServerService.initialize(); + } + + // ========== Tests for getInternalAuthServerMetadata() ========== + + @Test + public void getInternalAuthServerMetadata_Success() { + authorizationServerService.initialize(); + + AuthorizationServerMetadata metadata = authorizationServerService.getInternalAuthServerMetadata(); + + assertNotNull(metadata); + assertEquals(INTERNAL_SERVER_URL, metadata.getIssuer()); + assertEquals(INTERNAL_SERVER_URL + "/token", metadata.getTokenEndpoint()); + assertEquals(INTERNAL_SERVER_URL + "/authorize", metadata.getAuthorizationEndpoint()); + assertEquals(INTERNAL_SERVER_URL + "/jwks.json", metadata.getJwksUri()); + } + + @Test + public void getInternalAuthServerMetadata_NormalizesTrailingSlash() { + ReflectionTestUtils.setField(authorizationServerService, "internalAuthServerUrl", + INTERNAL_SERVER_URL + "/"); + authorizationServerService.initialize(); + + AuthorizationServerMetadata metadata = authorizationServerService.getInternalAuthServerMetadata(); + + assertEquals(INTERNAL_SERVER_URL, metadata.getIssuer()); + } + + // ========== Tests for discoverMetadata() ========== + + @Test + public void discoverMetadata_CacheHit_ReturnsCachedMetadata() { + AuthorizationServerMetadata cachedMetadata = AuthorizationServerMetadata.builder() + .issuer(EXTERNAL_SERVER_URL) + .tokenEndpoint(EXTERNAL_SERVER_URL + "/token") + .build(); + + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(cachedMetadata); + + AuthorizationServerMetadata result = authorizationServerService.discoverMetadata(EXTERNAL_SERVER_URL); + + assertEquals(cachedMetadata, result); + verify(restTemplate, never()).getForEntity(any(URI.class), eq(String.class)); + } + + @Test + public void discoverMetadata_CacheMiss_DiscoverFromOIDC_Success() throws Exception { + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(null); + + String metadataJson = "{\"issuer\":\"" + EXTERNAL_SERVER_URL + "\",\"token_endpoint\":\"" + EXTERNAL_SERVER_URL + "/token\"}"; + ResponseEntity response = new ResponseEntity<>(metadataJson, HttpStatus.OK); + + when(restTemplate.getForEntity(any(URI.class), eq(String.class))).thenReturn(response); + + AuthorizationServerMetadata expectedMetadata = AuthorizationServerMetadata.builder() + .issuer(EXTERNAL_SERVER_URL) + .tokenEndpoint(EXTERNAL_SERVER_URL + "/token") + .build(); + + when(objectMapper.readValue(eq(metadataJson), eq(AuthorizationServerMetadata.class))) + .thenReturn(expectedMetadata); + + AuthorizationServerMetadata result = authorizationServerService.discoverMetadata(EXTERNAL_SERVER_URL); + + assertNotNull(result); + assertEquals(EXTERNAL_SERVER_URL, result.getIssuer()); + verify(vciCacheService).setASMetadata(eq(EXTERNAL_SERVER_URL), eq(expectedMetadata)); + } + + @Test + public void discoverMetadata_OIDCFails_FallbackToOAuth_Success() throws Exception { + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(null); + + // First call to OIDC endpoint fails + ResponseEntity failedResponse = new ResponseEntity<>(null, HttpStatus.NOT_FOUND); + + // Second call to OAuth AS endpoint succeeds + String metadataJson = "{\"issuer\":\"" + EXTERNAL_SERVER_URL + "\",\"token_endpoint\":\"" + EXTERNAL_SERVER_URL + "/token\"}"; + ResponseEntity successResponse = new ResponseEntity<>(metadataJson, HttpStatus.OK); + + when(restTemplate.getForEntity(any(URI.class), eq(String.class))) + .thenReturn(failedResponse) + .thenReturn(failedResponse) + .thenReturn(failedResponse) // 3 retries for OIDC + .thenReturn(successResponse); + + AuthorizationServerMetadata expectedMetadata = AuthorizationServerMetadata.builder() + .issuer(EXTERNAL_SERVER_URL) + .tokenEndpoint(EXTERNAL_SERVER_URL + "/token") + .build(); + + when(objectMapper.readValue(eq(metadataJson), eq(AuthorizationServerMetadata.class))) + .thenReturn(expectedMetadata); + + AuthorizationServerMetadata result = authorizationServerService.discoverMetadata(EXTERNAL_SERVER_URL); + + assertNotNull(result); + } + + @Test + public void discoverMetadata_AllAttemptsFail_ThrowsCertifyException() throws Exception { + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(null); + + // All calls fail + ResponseEntity failedResponse = new ResponseEntity<>(null, HttpStatus.NOT_FOUND); + when(restTemplate.getForEntity(any(URI.class), eq(String.class))).thenReturn(failedResponse); + + CertifyException exception = assertThrows(CertifyException.class, + () -> authorizationServerService.discoverMetadata(EXTERNAL_SERVER_URL)); + + assertEquals(ErrorConstants.AUTHORIZATION_SERVER_DISCOVERY_FAILED, exception.getErrorCode()); + } + + @Test + public void discoverMetadata_InvalidMetadata_ThrowsCertifyException() throws Exception { + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(null); + + String metadataJson = "{\"issuer\":\"\"}"; // Missing token_endpoint + ResponseEntity response = new ResponseEntity<>(metadataJson, HttpStatus.OK); + + when(restTemplate.getForEntity(any(URI.class), eq(String.class))).thenReturn(response); + + AuthorizationServerMetadata invalidMetadata = AuthorizationServerMetadata.builder() + .issuer("") + .build(); + + when(objectMapper.readValue(eq(metadataJson), eq(AuthorizationServerMetadata.class))) + .thenReturn(invalidMetadata); + + CertifyException exception = assertThrows(CertifyException.class, + () -> authorizationServerService.discoverMetadata(EXTERNAL_SERVER_URL)); + + assertEquals(ErrorConstants.AUTHORIZATION_SERVER_DISCOVERY_FAILED, exception.getErrorCode()); + } + + // ========== Tests for getTokenEndpoint() ========== + + @Test + public void getTokenEndpoint_Success() { + AuthorizationServerMetadata metadata = AuthorizationServerMetadata.builder() + .issuer(EXTERNAL_SERVER_URL) + .tokenEndpoint(EXTERNAL_SERVER_URL + "/token") + .build(); + + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(metadata); + + String tokenEndpoint = authorizationServerService.getTokenEndpoint(EXTERNAL_SERVER_URL); + + assertEquals(EXTERNAL_SERVER_URL + "/token", tokenEndpoint); + } + + // ========== Tests for getJwksUri() ========== + + @Test + public void getJwksUri_Success() { + AuthorizationServerMetadata metadata = AuthorizationServerMetadata.builder() + .issuer(EXTERNAL_SERVER_URL) + .tokenEndpoint(EXTERNAL_SERVER_URL + "/token") + .jwksUri(EXTERNAL_SERVER_URL + "/jwks.json") + .build(); + + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(metadata); + + String jwksUri = authorizationServerService.getJwksUri(EXTERNAL_SERVER_URL); + + assertEquals(EXTERNAL_SERVER_URL + "/jwks.json", jwksUri); + } + + // ========== Tests for supportsPreAuthorizedCodeGrant() ========== + + @Test + public void supportsPreAuthorizedCodeGrant_Supported_ReturnsTrue() { + AuthorizationServerMetadata metadata = AuthorizationServerMetadata.builder() + .issuer(EXTERNAL_SERVER_URL) + .tokenEndpoint(EXTERNAL_SERVER_URL + "/token") + .grantTypesSupported(Arrays.asList("authorization_code", Constants.PRE_AUTHORIZED_CODE_GRANT_TYPE)) + .build(); + + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(metadata); + + boolean result = authorizationServerService.supportsPreAuthorizedCodeGrant(EXTERNAL_SERVER_URL); + + assertTrue(result); + } + + @Test + public void supportsPreAuthorizedCodeGrant_NotSupported_ReturnsFalse() { + AuthorizationServerMetadata metadata = AuthorizationServerMetadata.builder() + .issuer(EXTERNAL_SERVER_URL) + .tokenEndpoint(EXTERNAL_SERVER_URL + "/token") + .grantTypesSupported(Arrays.asList("authorization_code")) + .build(); + + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(metadata); + + boolean result = authorizationServerService.supportsPreAuthorizedCodeGrant(EXTERNAL_SERVER_URL); + + assertFalse(result); + } + + @Test + public void supportsPreAuthorizedCodeGrant_NullGrantTypes_ReturnsFalse() { + AuthorizationServerMetadata metadata = AuthorizationServerMetadata.builder() + .issuer(EXTERNAL_SERVER_URL) + .tokenEndpoint(EXTERNAL_SERVER_URL + "/token") + .build(); + + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(metadata); + + boolean result = authorizationServerService.supportsPreAuthorizedCodeGrant(EXTERNAL_SERVER_URL); + + assertFalse(result); + } + + @Test + public void supportsPreAuthorizedCodeGrant_DiscoveryFails_ReturnsFalse() { + when(vciCacheService.getASMetadata(EXTERNAL_SERVER_URL)).thenReturn(null); + when(restTemplate.getForEntity(any(URI.class), eq(String.class))) + .thenThrow(new RuntimeException("Connection failed")); + + boolean result = authorizationServerService.supportsPreAuthorizedCodeGrant(EXTERNAL_SERVER_URL); + + assertFalse(result); + } + + // ========== Tests for getAuthorizationServerForCredentialConfig() ========== + + @Test + public void getAuthorizationServerForCredentialConfig_MappedAS_ReturnsMapping() throws Exception { + String configId = "test-config"; + String mappedUrl = "https://mapped-auth.example.com"; + + ReflectionTestUtils.setField(authorizationServerService, "authorizationServersConfig", mappedUrl); + ReflectionTestUtils.setField(authorizationServerService, "credentialConfigMappingJson", + "{\"" + configId + "\":\"" + mappedUrl + "\"}"); + + when(objectMapper.readValue(anyString(), any(TypeReference.class))) + .thenReturn(Map.of(configId, mappedUrl)); + + authorizationServerService.initialize(); + + String result = authorizationServerService.getAuthorizationServerForCredentialConfig(configId); + + assertEquals(mappedUrl, result); + } + + @Test + public void getAuthorizationServerForCredentialConfig_NoMapping_UsesDefault() throws Exception { + String configId = "unmapped-config"; + + ReflectionTestUtils.setField(authorizationServerService, "defaultAuthServer", DEFAULT_SERVER_URL); + ReflectionTestUtils.setField(authorizationServerService, "authorizationServersConfig", DEFAULT_SERVER_URL); + + authorizationServerService.initialize(); + + String result = authorizationServerService.getAuthorizationServerForCredentialConfig(configId); + + assertEquals(DEFAULT_SERVER_URL, result); + } + + @Test + public void getAuthorizationServerForCredentialConfig_NoDefaultOrMapping_UsesInternal() { + String configId = "some-config"; + + ReflectionTestUtils.setField(authorizationServerService, "defaultAuthServer", ""); + + authorizationServerService.initialize(); + + String result = authorizationServerService.getAuthorizationServerForCredentialConfig(configId); + + assertEquals(INTERNAL_SERVER_URL, result); + } + + @Test + public void getAuthorizationServerForCredentialConfig_NoASConfigured_ThrowsCertifyException() { + String configId = "some-config"; + + ReflectionTestUtils.setField(authorizationServerService, "internalAuthServerUrl", ""); + ReflectionTestUtils.setField(authorizationServerService, "defaultAuthServer", ""); + + authorizationServerService.initialize(); + + CertifyException exception = assertThrows(CertifyException.class, + () -> authorizationServerService.getAuthorizationServerForCredentialConfig(configId)); + + assertEquals(ErrorConstants.AUTHORIZATION_SERVER_NOT_CONFIGURED, exception.getErrorCode()); + } + + @Test + public void getAuthorizationServerForCredentialConfig_MappedButNotConfigured_ThrowsInvalidRequestException() throws Exception { + String configId = "test-config"; + String unconfiguredUrl = "https://unconfigured.example.com"; + + ReflectionTestUtils.setField(authorizationServerService, "credentialConfigMappingJson", + "{\"" + configId + "\":\"" + unconfiguredUrl + "\"}"); + + when(objectMapper.readValue(anyString(), any(TypeReference.class))) + .thenReturn(Map.of(configId, unconfiguredUrl)); + + authorizationServerService.initialize(); + + InvalidRequestException exception = assertThrows(InvalidRequestException.class, + () -> authorizationServerService.getAuthorizationServerForCredentialConfig(configId)); + + assertEquals(ErrorConstants.INVALID_AUTHORIZATION_SERVER, exception.getErrorCode()); + } + + // ========== Tests for getAllAuthorizationServerUrls() ========== + + @Test + public void getAllAuthorizationServerUrls_ReturnsAllConfigured() { + ReflectionTestUtils.setField(authorizationServerService, "authorizationServersConfig", + "https://ext1.example.com, https://ext2.example.com"); + + authorizationServerService.initialize(); + + List urls = authorizationServerService.getAllAuthorizationServerUrls(); + + assertEquals(3, urls.size()); + assertTrue(urls.contains(INTERNAL_SERVER_URL)); + assertTrue(urls.contains("https://ext1.example.com")); + assertTrue(urls.contains("https://ext2.example.com")); + } + + // ========== Tests for isServerConfigured() ========== + + @Test + public void isServerConfigured_ConfiguredServer_ReturnsTrue() { + authorizationServerService.initialize(); + + boolean result = authorizationServerService.isServerConfigured(INTERNAL_SERVER_URL); + + assertTrue(result); + } + + @Test + public void isServerConfigured_ConfiguredServerWithTrailingSlash_ReturnsTrue() { + authorizationServerService.initialize(); + + boolean result = authorizationServerService.isServerConfigured(INTERNAL_SERVER_URL + "/"); + + assertTrue(result); + } + + @Test + public void isServerConfigured_UnconfiguredServer_ReturnsFalse() { + authorizationServerService.initialize(); + + boolean result = authorizationServerService.isServerConfigured("https://unknown.example.com"); + + assertFalse(result); + } + + // ========== Tests for normalizeUrl() (accessed via reflection) ========== + + @Test + public void normalizeUrl_RemovesTrailingSlash() { + authorizationServerService.initialize(); + + String result = ReflectionTestUtils.invokeMethod(authorizationServerService, "normalizeUrl", + "https://example.com/"); + + assertEquals("https://example.com", result); + } + + @Test + public void normalizeUrl_RemovesMultipleTrailingSlashes() { + authorizationServerService.initialize(); + + String result = ReflectionTestUtils.invokeMethod(authorizationServerService, "normalizeUrl", + "https://example.com///"); + + assertEquals("https://example.com", result); + } + + @Test + public void normalizeUrl_NullUrl_ReturnsEmptyString() { + authorizationServerService.initialize(); + + String result = ReflectionTestUtils.invokeMethod(authorizationServerService, "normalizeUrl", + (String) null); + + assertEquals("", result); + } + + @Test + public void normalizeUrl_NoTrailingSlash_ReturnsSame() { + authorizationServerService.initialize(); + + String result = ReflectionTestUtils.invokeMethod(authorizationServerService, "normalizeUrl", + "https://example.com"); + + assertEquals("https://example.com", result); + } + + // ========== Tests for generateServerId() (accessed via reflection) ========== + + @Test + public void generateServerId_ValidUrl_GeneratesId() { + authorizationServerService.initialize(); + + String result = ReflectionTestUtils.invokeMethod(authorizationServerService, "generateServerId", + "https://auth.example.com"); + + assertNotNull(result); + assertTrue(result.startsWith("as-")); + assertTrue(result.contains("auth-example-com")); + } + + @Test + public void generateServerId_UrlWithPort_GeneratesId() { + authorizationServerService.initialize(); + + String result = ReflectionTestUtils.invokeMethod(authorizationServerService, "generateServerId", + "https://auth.example.com:8080"); + + assertNotNull(result); + assertTrue(result.startsWith("as-")); + } +} From 9efec1756b5f8961ade58bef72d75d6e20c4868b Mon Sep 17 00:00:00 2001 From: Amay Dixit Date: Fri, 19 Dec 2025 01:46:24 +0530 Subject: [PATCH 13/13] [INJICERT-1239] test(VCICacheService): add unit tests for Authorization Server Metadata caching Signed-off-by: amaydixit11 --- .../io/mosip/certify/VCICacheServiceTest.java | 69 +++++++++++++++++++ .../controller/WellKnownControllerTest.java | 62 ++++++++++++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/certify-service/src/test/java/io/mosip/certify/VCICacheServiceTest.java b/certify-service/src/test/java/io/mosip/certify/VCICacheServiceTest.java index 651c105c..442ddf5e 100644 --- a/certify-service/src/test/java/io/mosip/certify/VCICacheServiceTest.java +++ b/certify-service/src/test/java/io/mosip/certify/VCICacheServiceTest.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.mosip.certify.core.constants.Constants; +import io.mosip.certify.core.dto.AuthorizationServerMetadata; import io.mosip.certify.core.dto.CredentialIssuerMetadataVD13DTO; import io.mosip.certify.core.dto.CredentialOfferResponse; import io.mosip.certify.core.dto.PreAuthCodeData; @@ -289,4 +290,72 @@ public void getTransactionByToken_WhenNotFound_ReturnsNull() { assertEquals(null, result); } + + // Tests for setASMetadata and getASMetadata + + private static final String AS_METADATA_CACHE = "asMetadataCache"; + + @Test + public void setASMetadata_Success() { + String serverUrl = "https://auth.example.com"; + AuthorizationServerMetadata metadata = AuthorizationServerMetadata.builder() + .issuer(serverUrl) + .tokenEndpoint(serverUrl + "/token") + .build(); + + when(cacheManager.getCache(AS_METADATA_CACHE)).thenReturn(cache); + + vciCacheService.setASMetadata(serverUrl, metadata); + + verify(cacheManager).getCache(AS_METADATA_CACHE); + verify(cache).put(eq(Constants.AS_METADATA_PREFIX + serverUrl), eq(metadata)); + } + + @Test(expected = IllegalStateException.class) + public void setASMetadata_WhenCacheIsNull_ThrowsIllegalStateException() { + when(cacheManager.getCache(AS_METADATA_CACHE)).thenReturn(null); + + vciCacheService.setASMetadata("https://auth.example.com", + AuthorizationServerMetadata.builder().build()); + } + + @Test + public void getASMetadata_CacheHit_ReturnsMetadata() { + String serverUrl = "https://auth.example.com"; + AuthorizationServerMetadata metadata = AuthorizationServerMetadata.builder() + .issuer(serverUrl) + .tokenEndpoint(serverUrl + "/token") + .build(); + + Cache.ValueWrapper wrapper = mock(Cache.ValueWrapper.class); + when(wrapper.get()).thenReturn(metadata); + when(cacheManager.getCache(AS_METADATA_CACHE)).thenReturn(cache); + when(cache.get(Constants.AS_METADATA_PREFIX + serverUrl)).thenReturn(wrapper); + + AuthorizationServerMetadata result = vciCacheService.getASMetadata(serverUrl); + + assertEquals(metadata, result); + verify(cache).get(Constants.AS_METADATA_PREFIX + serverUrl); + } + + @Test + public void getASMetadata_CacheMiss_ReturnsNull() { + String serverUrl = "https://auth.example.com"; + + when(cacheManager.getCache(AS_METADATA_CACHE)).thenReturn(cache); + when(cache.get(Constants.AS_METADATA_PREFIX + serverUrl)).thenReturn(null); + + AuthorizationServerMetadata result = vciCacheService.getASMetadata(serverUrl); + + assertEquals(null, result); + } + + @Test + public void getASMetadata_WhenCacheIsNull_ReturnsNull() { + when(cacheManager.getCache(AS_METADATA_CACHE)).thenReturn(null); + + AuthorizationServerMetadata result = vciCacheService.getASMetadata("https://auth.example.com"); + + assertEquals(null, result); + } } diff --git a/certify-service/src/test/java/io/mosip/certify/controller/WellKnownControllerTest.java b/certify-service/src/test/java/io/mosip/certify/controller/WellKnownControllerTest.java index 181814cf..9d3b5fa2 100644 --- a/certify-service/src/test/java/io/mosip/certify/controller/WellKnownControllerTest.java +++ b/certify-service/src/test/java/io/mosip/certify/controller/WellKnownControllerTest.java @@ -73,7 +73,7 @@ void getCredentialIssuerMetadata_unsupportedVersion_returnsError() throws Except when(credentialConfigurationService.fetchCredentialIssuerMetadata("unsupported")) .thenThrow(new CertifyException("UNSUPPORTED_VERSION", "Unsupported version")); mockMvc.perform(get("/.well-known/openid-credential-issuer?version=unsupported")) - .andExpect(status().is2xxSuccessful()) + .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.errors[0].errorCode").value("UNSUPPORTED_VERSION")); } @@ -99,7 +99,65 @@ void getDIDDocument_serviceThrowsException_returnsError() throws Exception { when(vcIssuanceService.getDIDDocument()) .thenThrow(new InvalidRequestException("unsupported_in_current_plugin_mode")); mockMvc.perform(get("/.well-known/did.json")) - .andExpect(status().is2xxSuccessful()) + .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.errors[0].errorCode").value("unsupported_in_current_plugin_mode")); } + + @Test + void getAuthorizationServerMetadata_success() throws Exception { + io.mosip.certify.core.dto.AuthorizationServerMetadata mockMetadata = + io.mosip.certify.core.dto.AuthorizationServerMetadata.builder() + .issuer("https://auth.example.com") + .tokenEndpoint("https://auth.example.com/token") + .authorizationEndpoint("https://auth.example.com/authorize") + .jwksUri("https://auth.example.com/jwks.json") + .build(); + + when(authorizationServerService.getInternalAuthServerMetadata()).thenReturn(mockMetadata); + + mockMvc.perform(get("/.well-known/oauth-authorization-server")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.issuer").value("https://auth.example.com")) + .andExpect(jsonPath("$.token_endpoint").value("https://auth.example.com/token")) + .andExpect(jsonPath("$.authorization_endpoint").value("https://auth.example.com/authorize")) + .andExpect(jsonPath("$.jwks_uri").value("https://auth.example.com/jwks.json")); + + verify(authorizationServerService, times(1)).getInternalAuthServerMetadata(); + } + + @Test + void getOpenIDConfiguration_success() throws Exception { + io.mosip.certify.core.dto.AuthorizationServerMetadata mockMetadata = + io.mosip.certify.core.dto.AuthorizationServerMetadata.builder() + .issuer("https://auth.example.com") + .tokenEndpoint("https://auth.example.com/token") + .build(); + + when(authorizationServerService.getInternalAuthServerMetadata()).thenReturn(mockMetadata); + + mockMvc.perform(get("/.well-known/openid-configuration")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.issuer").value("https://auth.example.com")) + .andExpect(jsonPath("$.token_endpoint").value("https://auth.example.com/token")); + + verify(authorizationServerService, times(1)).getInternalAuthServerMetadata(); + } + + @Test + void getAuthorizationServerMetadata_returnsNullFields_success() throws Exception { + io.mosip.certify.core.dto.AuthorizationServerMetadata mockMetadata = + io.mosip.certify.core.dto.AuthorizationServerMetadata.builder() + .issuer("https://auth.example.com") + .tokenEndpoint("https://auth.example.com/token") + .build(); + + when(authorizationServerService.getInternalAuthServerMetadata()).thenReturn(mockMetadata); + + mockMvc.perform(get("/.well-known/oauth-authorization-server")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.issuer").value("https://auth.example.com")) + .andExpect(jsonPath("$.token_endpoint").value("https://auth.example.com/token")) + .andExpect(jsonPath("$.authorization_endpoint").doesNotExist()) + .andExpect(jsonPath("$.jwks_uri").doesNotExist()); + } }