From 2ad7f56f6a3e3d768ac97da090c4a29fceeb1312 Mon Sep 17 00:00:00 2001 From: rubenmodamioin2 <145454887+rubenmodamioin2@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:33:57 +0200 Subject: [PATCH] Feature/coverage (#44) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Hotfix for dome verifier * Update build.gradle * Update ApplicationRegexPattern.java * Fix url mapping * Url mapping fix * fix(broker): solve confusion between internal and external * test(broker): fix tests to check get internal url and entitites path * test(broker): refactor test * test(util): tests to format url * Fix Vp for dome * Fix verifiable presentation for dome flow * Added signed VP for dome * Remove log * refactor(broker): improve naming * Update PresentationServiceImpl.java * tests * Tests --------- Co-authored-by: Albert Rodríguez --- .../core/config/WebSocketSessionManager.java | 6 +- .../PinRequestWebSocketHandlerTest.java | 146 ++++++++++++++++++ .../wallet/api/config/SwaggerConfigTest.java | 45 ++++++ .../config/WebSocketSessionManagerTest.java | 51 ++++++ .../QrCodeProcessorControllerTest.java | 65 ++++++++ .../AuthorisationServerMetadataTest.java | 114 ++++++++++++++ .../api/model/VerifiableCredentialTest.java | 61 ++++++++ 7 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 src/test/java/es/in2/wallet/api/config/PinRequestWebSocketHandlerTest.java create mode 100644 src/test/java/es/in2/wallet/api/config/SwaggerConfigTest.java create mode 100644 src/test/java/es/in2/wallet/api/config/WebSocketSessionManagerTest.java create mode 100644 src/test/java/es/in2/wallet/api/controller/QrCodeProcessorControllerTest.java create mode 100644 src/test/java/es/in2/wallet/api/model/AuthorisationServerMetadataTest.java create mode 100644 src/test/java/es/in2/wallet/api/model/VerifiableCredentialTest.java diff --git a/src/main/java/es/in2/wallet/infrastructure/core/config/WebSocketSessionManager.java b/src/main/java/es/in2/wallet/infrastructure/core/config/WebSocketSessionManager.java index e918088..b56e388 100644 --- a/src/main/java/es/in2/wallet/infrastructure/core/config/WebSocketSessionManager.java +++ b/src/main/java/es/in2/wallet/infrastructure/core/config/WebSocketSessionManager.java @@ -8,11 +8,7 @@ import java.util.concurrent.ConcurrentHashMap; @Component -public class - - - -WebSocketSessionManager { +public class WebSocketSessionManager { private final Map userSessions = new ConcurrentHashMap<>(); diff --git a/src/test/java/es/in2/wallet/api/config/PinRequestWebSocketHandlerTest.java b/src/test/java/es/in2/wallet/api/config/PinRequestWebSocketHandlerTest.java new file mode 100644 index 0000000..282f6f6 --- /dev/null +++ b/src/test/java/es/in2/wallet/api/config/PinRequestWebSocketHandlerTest.java @@ -0,0 +1,146 @@ +package es.in2.wallet.api.config; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import es.in2.wallet.domain.exception.ParseErrorException; +import es.in2.wallet.domain.model.WebSocketClientMessage; +import es.in2.wallet.domain.model.WebSocketServerMessage; +import es.in2.wallet.infrastructure.core.config.PinRequestWebSocketHandler; +import es.in2.wallet.infrastructure.core.config.WebSocketSessionManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.web.reactive.socket.WebSocketMessage; +import org.springframework.web.reactive.socket.WebSocketSession; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Sinks; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PinRequestWebSocketHandlerTest { + + @Mock + private ObjectMapper objectMapper; + + @Mock + private WebSocketSessionManager sessionManager; + + @InjectMocks + private PinRequestWebSocketHandler handler; + + @Mock + private WebSocketSession session; + + @BeforeEach + void setUp() { + handler = new PinRequestWebSocketHandler(objectMapper, sessionManager); + } + + @Test + void testHandleIdMessage() throws Exception { + String jwtToken = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxOGFyVmZaZTJpQkJoaU56RURnT3c3Tlc1ZmZHNElLTEtOSmVIOFQxdjJNIn0.eyJleHAiOjE3MTgzNjU3MjUsImlhdCI6MTcxODM2NTQyNSwiYXV0aF90aW1lIjoxNzE4MzUyODA1LCJqdGkiOiJlZWFmNWRlNy0wODc5LTRkYTktOGMwYS0yMGIzZDIwNWZjNGIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjcwMDIvcmVhbG1zL3dhbGxldCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIyYzk5NTFkMi04NmNjLTQ0ZGYtOGQ2Mi0zNDIyN2NmYmVmOWMiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhdXRoLWNsaWVudCIsIm5vbmNlIjoiYjVkZGVhZDE3ZGU2YjhmNzkyZDZkN2MwMzY4NTFlZjU3MGdRRjlxdDIiLCJzZXNzaW9uX3N0YXRlIjoiNjBkYjRiM2UtM2MzMi00NGY2LTk0YzItZGEzOGYyNTFmODc5IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjQyMDIiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJ1c2VyIiwiZGVmYXVsdC1yb2xlcy13YWxsZXQiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBvZmZsaW5lX2FjY2VzcyBlbWFpbCBwcm9maWxlIiwic2lkIjoiNjBkYjRiM2UtM2MzMi00NGY2LTk0YzItZGEzOGYyNTFmODc5IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidXNlciB3YWxsZXQiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyIiwiZ2l2ZW5fbmFtZSI6InVzZXIiLCJmYW1pbHlfbmFtZSI6IndhbGxldCIsImVtYWlsIjoidXNlcndhbGxldEBleGFtcGxlLmNvbSJ9.iQCM2Yxlw68-6r2aIM1XAU9aT_fK7dMOliwTX_wwZhORmk3D8qkFBLfg_6JnWyFE0lRYq_NP__mJZXneXFbnjWkXsEN4WyuuIzb-jRc1REu9A0b40N3Gt-JfjU1GEKw-4SkrG8tUsgM6lxCI0DEP1_V9z47YwDRkT50DzdtwBMa7aKQ3f3o3Cla_fCG2c0CKk6LsCYi9wOth2dEknRhqaEwwk1BXopsScE1hqB-evY-sYjETEK081tXaAbk5Mdsbp7tdWTsRoVhaDGSOB6ZzlKVscGP8KWPjD6DSmKfEGaLG7X8lKXMqhMaeT9UpgXGtWzi7Ey9E7OstB0APLhaoEA"; + + String payload = """ + { + "id": "%s" + } + """.formatted(jwtToken); + + WebSocketClientMessage message = new WebSocketClientMessage(jwtToken, null); + WebSocketMessage webSocketMessage = mock(WebSocketMessage.class); + + when(webSocketMessage.getPayloadAsText()).thenReturn(payload); + when(session.receive()).thenReturn(Flux.just(webSocketMessage)); + when(objectMapper.readValue(payload, WebSocketClientMessage.class)).thenReturn(message); + when(session.getId()).thenReturn("sessionId"); + + StepVerifier.create(handler.handle(session)) + .verifyComplete(); + } + + @Test + void testHandlePinMessage() throws Exception { + String payload = "{\"pin\":\"1234\"}"; + WebSocketClientMessage message = new WebSocketClientMessage(null, "1234"); + WebSocketMessage webSocketMessage = mock(WebSocketMessage.class); + + when(webSocketMessage.getPayloadAsText()).thenReturn(payload); + when(session.receive()).thenReturn(Flux.just(webSocketMessage)); + when(objectMapper.readValue(payload, WebSocketClientMessage.class)).thenReturn(message); + when(session.getId()).thenReturn("sessionId"); + + handler.getSessionToUserIdMap().put("sessionId", "testUser"); + Sinks.Many sink = Sinks.many().multicast().directBestEffort(); + handler.getPinSinks().put("testUser", sink); + + StepVerifier.create(handler.handle(session)) + .then(sink::tryEmitComplete) + .verifyComplete(); + + StepVerifier.create(sink.asFlux()) + .verifyComplete(); + } + + + @Test + void testSendPinRequest() throws JsonProcessingException { + WebSocketServerMessage serverMessage = new WebSocketServerMessage(null,true); + WebSocketMessage webSocketMessage = mock(WebSocketMessage.class); + + String jsonMessage = "{\"pin\":\"true\"}"; + + when(objectMapper.writeValueAsString(serverMessage)).thenReturn(jsonMessage); + when(session.textMessage(jsonMessage)).thenReturn(webSocketMessage); + when(session.send(any())).thenReturn(Mono.empty()); + + handler.sendPinRequest(session, serverMessage); + + verify(session, times(1)).send(any()); + } + + @Test + void testSendPinRequestSerializationError() throws JsonProcessingException { + WebSocketServerMessage serverMessage = new WebSocketServerMessage(null,true); + + when(objectMapper.writeValueAsString(serverMessage)).thenThrow(JsonProcessingException.class); + + assertThrows(ParseErrorException.class, () -> handler.sendPinRequest(session, serverMessage)); + + verify(session, never()).send(any()); + } + + @Test + void testGetPinResponses() { + String userId = "testUser"; + Sinks.Many sink = Sinks.many().multicast().directBestEffort(); + handler.getPinSinks().put(userId, sink); + + Flux pinResponses = handler.getPinResponses(userId); + + StepVerifier.create(pinResponses) + .then(() -> sink.tryEmitNext("1234")) + .expectNext("1234") + .thenCancel() + .verify(); + } + + @Test + void testGetPinResponsesNoSink() { + String userId = "unknownUser"; + + Flux pinResponses = handler.getPinResponses(userId); + + StepVerifier.create(pinResponses) + .thenCancel() + .verify(); + } +} + diff --git a/src/test/java/es/in2/wallet/api/config/SwaggerConfigTest.java b/src/test/java/es/in2/wallet/api/config/SwaggerConfigTest.java new file mode 100644 index 0000000..f30cf27 --- /dev/null +++ b/src/test/java/es/in2/wallet/api/config/SwaggerConfigTest.java @@ -0,0 +1,45 @@ +package es.in2.wallet.api.config; + +import es.in2.wallet.infrastructure.core.config.SwaggerConfig; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; +import org.springdoc.core.customizers.OpenApiCustomizer; +import org.springdoc.core.models.GroupedOpenApi; + +import static org.assertj.core.api.Assertions.assertThat; + +class SwaggerConfigTest { + + private SwaggerConfig swaggerConfig; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + swaggerConfig = new SwaggerConfig(); + } + + @Test + void testPublicApi() { + GroupedOpenApi publicApi = swaggerConfig.publicApi(); + assertThat(publicApi).isNotNull(); + assertThat(publicApi.getGroup()).isEqualTo("Public API"); + assertThat(publicApi.getPathsToMatch()).containsExactly("/**"); + + // Verify the customizer is configured correctly + OpenApiCustomizer customizer = publicApi.getOpenApiCustomizers().get(0); + assertThat(customizer).isNotNull(); + } + + @Test + void testPrivateApi() { + GroupedOpenApi privateApi = swaggerConfig.privateApi(); + assertThat(privateApi).isNotNull(); + assertThat(privateApi.getGroup()).isEqualTo("Private API"); + assertThat(privateApi.getPathsToMatch()).containsExactly("/**"); + + // Verify the customizer is configured correctly + OpenApiCustomizer customizer = privateApi.getOpenApiCustomizers().get(0); + assertThat(customizer).isNotNull(); + } +} diff --git a/src/test/java/es/in2/wallet/api/config/WebSocketSessionManagerTest.java b/src/test/java/es/in2/wallet/api/config/WebSocketSessionManagerTest.java new file mode 100644 index 0000000..9f9eb3e --- /dev/null +++ b/src/test/java/es/in2/wallet/api/config/WebSocketSessionManagerTest.java @@ -0,0 +1,51 @@ +package es.in2.wallet.api.config; +import es.in2.wallet.infrastructure.core.config.WebSocketSessionManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.web.reactive.socket.WebSocketSession; +import reactor.test.StepVerifier; + +import static org.mockito.Mockito.mock; + +class WebSocketSessionManagerTest { + + @Mock + private WebSocketSession session; + + private WebSocketSessionManager webSocketSessionManager; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + webSocketSessionManager = new WebSocketSessionManager(); + } + + @Test + void testRegisterSession() { + String userId = "user123"; + + webSocketSessionManager.registerSession(userId, session); + + // Verificar que la sesión se ha registrado correctamente + StepVerifier.create(webSocketSessionManager.getSession(userId)) + .expectNext(session) + .verifyComplete(); + } + + @Test + void testGetSessionWhenSessionExists() { + String userId = "user123"; + WebSocketSession existingSession = mock(WebSocketSession.class); + + // Registrar la sesión + webSocketSessionManager.registerSession(userId, existingSession); + + // Verificar que la sesión se obtiene correctamente + StepVerifier.create(webSocketSessionManager.getSession(userId)) + .expectNext(existingSession) + .verifyComplete(); + } +} + diff --git a/src/test/java/es/in2/wallet/api/controller/QrCodeProcessorControllerTest.java b/src/test/java/es/in2/wallet/api/controller/QrCodeProcessorControllerTest.java new file mode 100644 index 0000000..e45ea9f --- /dev/null +++ b/src/test/java/es/in2/wallet/api/controller/QrCodeProcessorControllerTest.java @@ -0,0 +1,65 @@ +package es.in2.wallet.api.controller; + +import es.in2.wallet.domain.model.QrContent; +import es.in2.wallet.domain.service.QrCodeProcessorService; +import es.in2.wallet.infrastructure.core.controller.QrCodeProcessorController; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpHeaders; +import org.springframework.test.web.reactive.server.WebTestClient; +import reactor.core.publisher.Mono; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class QrCodeProcessorControllerTest { + + @Mock + private QrCodeProcessorService qrCodeProcessorService; + + @InjectMocks + private QrCodeProcessorController qrCodeProcessorController; + + @Test + void testExecuteQrContent() { + // Arrange + String authorizationHeader = "Bearer authToken"; + QrContent qrContent = QrContent.builder().content("qrCodeContent").build(); + + when(qrCodeProcessorService.processQrContent(anyString(), anyString(), anyString())).thenReturn(Mono.empty()); + + WebTestClient + .bindToController(qrCodeProcessorController) + .build() + .post() + .uri("/api/v1/execute-content") + .header(HttpHeaders.AUTHORIZATION, authorizationHeader) + .bodyValue(qrContent) + .exchange() + .expectStatus().isCreated(); + } + + @Test + void testExecuteQrContentWithError() { + // Arrange + String authorizationHeader = "Bearer authToken"; + QrContent qrContent = QrContent.builder().content("qrCodeContent").build(); + + when(qrCodeProcessorService.processQrContent(anyString(), anyString(), anyString())).thenReturn(Mono.error(new RuntimeException("Error processing QR content"))); + + WebTestClient + .bindToController(qrCodeProcessorController) + .build() + .post() + .uri("/api/v1/execute-content") + .header(HttpHeaders.AUTHORIZATION, authorizationHeader) + .bodyValue(qrContent) + .exchange() + .expectStatus().is5xxServerError(); + } +} + diff --git a/src/test/java/es/in2/wallet/api/model/AuthorisationServerMetadataTest.java b/src/test/java/es/in2/wallet/api/model/AuthorisationServerMetadataTest.java new file mode 100644 index 0000000..a01ce88 --- /dev/null +++ b/src/test/java/es/in2/wallet/api/model/AuthorisationServerMetadataTest.java @@ -0,0 +1,114 @@ +package es.in2.wallet.api.model; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import es.in2.wallet.domain.model.AuthorisationServerMetadata; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class AuthorisationServerMetadataTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testSerialization() throws JsonProcessingException { + AuthorisationServerMetadata.AuthenticationMethodsSupported authMethodsSupported = + AuthorisationServerMetadata.AuthenticationMethodsSupported.builder() + .authorizationEndpoint(List.of("auth_endpoint1", "auth_endpoint2")) + .build(); + + AuthorisationServerMetadata.VpFormatsSupported.AlgValuesSupported algValuesSupported = + AuthorisationServerMetadata.VpFormatsSupported.AlgValuesSupported.builder() + .algValuesSupported(List.of("RS256", "ES256")) + .build(); + + AuthorisationServerMetadata.VpFormatsSupported vpFormatsSupported = + AuthorisationServerMetadata.VpFormatsSupported.builder() + .jwtVp(algValuesSupported) + .jwtVc(algValuesSupported) + .build(); + + AuthorisationServerMetadata metadata = AuthorisationServerMetadata.builder() + .redirectUris(List.of("https://example.com/redirect")) + .issuer("https://example.com") + .authorizationEndpoint("https://example.com/authorize") + .tokenEndpoint("https://example.com/token") + .userInfoEndpoint("https://example.com/userinfo") + .presentationDefinitionEndpoint("https://example.com/presentation") + .jwksUri("https://example.com/jwks") + .scopesSupported(List.of("openid", "profile")) + .responseTypesSupported(List.of("code", "token")) + .responseModesSupported(List.of("query", "fragment")) + .grantTypesSupported(List.of("authorization_code", "client_credentials")) + .subjectTypesSupported(List.of("public", "pairwise")) + .idTokenSigningAlgValuesSupported(List.of("RS256", "ES256")) + .requestObjectSigningAlgValuesSupported(List.of("RS256")) + .requestParameterSupported(true) + .requestUriParameterSupported(true) + .tokenEndpointAuthMethodsSupported(List.of("client_secret_basic", "client_secret_post")) + .requestAuthenticationMethodsSupported(authMethodsSupported) + .vpFormatsSupported(vpFormatsSupported) + .subjectSyntaxTypesSupported(List.of("subjectSyntax1", "subjectSyntax2")) + .subjectSyntaxTypesDiscriminations(List.of("discrimination1", "discrimination2")) + .subjectTrustFrameworksSupported(List.of("trustFramework1", "trustFramework2")) + .idTokenTypesSupported(List.of("JWT", "ID_TOKEN")) + .build(); + + String json = objectMapper.writeValueAsString(metadata); + assertThat(json).contains("https://example.com","RS256"); + } + + @Test + void testDeserialization() throws JsonProcessingException { + String json = """ + { + "redirect_uris": ["https://example.com/redirect"], + "issuer": "https://example.com", + "authorization_endpoint": "https://example.com/authorize", + "token_endpoint": "https://example.com/token", + "userinfo_endpoint": "https://example.com/userinfo", + "presentation_definition_endpoint": "https://example.com/presentation", + "jwks_uri": "https://example.com/jwks", + "scopes_supported": ["openid", "profile"], + "response_types_supported": ["code", "token"], + "response_modes_supported": ["query", "fragment"], + "grant_types_supported": ["authorization_code", "client_credentials"], + "subject_types_supported": ["public", "pairwise"], + "id_token_signing_alg_values_supported": ["RS256", "ES256"], + "request_object_signing_alg_values_supported": ["RS256"], + "request_parameter_supported": true, + "request_uri_parameter_supported": true, + "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"], + "request_authentication_methods_supported": { + "authorization_endpoint": ["auth_endpoint1", "auth_endpoint2"] + }, + "vp_formats_supported": { + "jwt_vp": { + "alg_values_supported": ["RS256", "ES256"] + }, + "jwt_vc": { + "alg_values_supported": ["RS256", "ES256"] + } + }, + "subject_syntax_types_supported": ["subjectSyntax1", "subjectSyntax2"], + "subject_syntax_types_discriminations": ["discrimination1", "discrimination2"], + "subject_trust_frameworks_supported": ["trustFramework1", "trustFramework2"], + "id_token_types_supported": ["JWT", "ID_TOKEN"] + } + """; + + AuthorisationServerMetadata metadata = objectMapper.readValue(json, AuthorisationServerMetadata.class); + + assertThat(metadata.issuer()).isEqualTo("https://example.com"); + assertThat(metadata.idTokenSigningAlgValuesSupported()).contains("RS256", "ES256"); + assertThat(metadata.requestAuthenticationMethodsSupported().authorizationEndpoint()).contains("auth_endpoint1", "auth_endpoint2"); + assertThat(metadata.vpFormatsSupported().jwtVp().algValuesSupported()).contains("RS256", "ES256"); + } +} + diff --git a/src/test/java/es/in2/wallet/api/model/VerifiableCredentialTest.java b/src/test/java/es/in2/wallet/api/model/VerifiableCredentialTest.java new file mode 100644 index 0000000..1c2f318 --- /dev/null +++ b/src/test/java/es/in2/wallet/api/model/VerifiableCredentialTest.java @@ -0,0 +1,61 @@ +package es.in2.wallet.api.model; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import es.in2.wallet.domain.model.VerifiableCredential; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class VerifiableCredentialTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testSerialization() throws JsonProcessingException { + JsonNode issuerNode = objectMapper.createObjectNode().put("id", "did:example:issuer"); + JsonNode credentialSubjectNode = objectMapper.createObjectNode().put("id", "did:example:subject"); + + VerifiableCredential verifiableCredential = VerifiableCredential.builder() + .type(List.of("VerifiableCredential", "ExampleCredential")) + .context(List.of("https://www.w3.org/2018/credentials/v1")) + .id("urn:uuid:1234") + .issuer(issuerNode) + .issuanceDate("2020-01-01T00:00:00Z") + .issued("2020-01-01T00:00:00Z") + .validFrom("2020-01-01T00:00:00Z") + .expirationDate("2021-01-01T00:00:00Z") + .credentialSubject(credentialSubjectNode) + .build(); + + String json = objectMapper.writeValueAsString(verifiableCredential); + assertThat(json).contains("VerifiableCredential", "https://www.w3.org/2018/credentials/v1", "urn:uuid:1234"); + } + + @Test + void testDeserialization() throws JsonProcessingException { + String json = """ + { + "type": ["VerifiableCredential", "ExampleCredential"], + "@context": ["https://www.w3.org/2018/credentials/v1"], + "id": "urn:uuid:1234", + "issuer": {"id": "did:example:issuer"}, + "issuanceDate": "2020-01-01T00:00:00Z", + "issued": "2020-01-01T00:00:00Z", + "validFrom": "2020-01-01T00:00:00Z", + "expirationDate": "2021-01-01T00:00:00Z", + "credentialSubject": {"id": "did:example:subject"} + } + """; + + VerifiableCredential verifiableCredential = objectMapper.readValue(json, VerifiableCredential.class); + + assertThat(verifiableCredential.id()).isEqualTo("urn:uuid:1234"); + assertThat(verifiableCredential.type()).contains("VerifiableCredential", "ExampleCredential"); + assertThat(verifiableCredential.issuer().get("id").asText()).isEqualTo("did:example:issuer"); + assertThat(verifiableCredential.credentialSubject().get("id").asText()).isEqualTo("did:example:subject"); + } +}