Skip to content
Snippets Groups Projects
Commit e5316748 authored by OZGCloud's avatar OZGCloud
Browse files

OZG-7092 add call context interceptor

parent 9983f0f0
No related branches found
No related tags found
1 merge request!1OZG-7092 Anpassung TokenChecker
......@@ -26,7 +26,7 @@
<parent>
<groupId>de.ozgcloud.common</groupId>
<artifactId>ozgcloud-common-parent</artifactId>
<version>4.6.0</version>
<version>4.7.0-SNAPSHOT</version>
<relativePath/>
</parent>
......
/*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.token.common;
import java.util.UUID;
import org.apache.logging.log4j.CloseableThreadContext;
import de.ozgcloud.common.grpc.GrpcUtil;
import io.grpc.ForwardingServerCallListener;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCall.Listener;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import lombok.RequiredArgsConstructor;
import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor;
@GrpcGlobalServerInterceptor
@RequiredArgsConstructor
class CallContextGrpcServerInterceptor implements ServerInterceptor {
@Override
public <A, B> Listener<A> interceptCall(ServerCall<A, B> call, Metadata headers, ServerCallHandler<A, B> next) {
return new LogContextSettingListener<>(next.startCall(call, headers), headers);
}
class LogContextSettingListener<A> extends ForwardingServerCallListener.SimpleForwardingServerCallListener<A> {
private final String requestId;
public LogContextSettingListener(Listener<A> delegate, Metadata headers) {
super(delegate);
this.requestId = getRequestId(headers);
}
String getRequestId(Metadata headers) {
return GrpcUtil.getRequestId(headers).orElseGet(() -> UUID.randomUUID().toString());
}
@Override
public void onMessage(A message) {
doSurroundOn(() -> super.onMessage(message));
}
@Override
public void onHalfClose() {
doSurroundOn(super::onHalfClose);
}
@Override
public void onCancel() {
doSurroundOn(super::onCancel);
}
@Override
public void onComplete() {
doSurroundOn(super::onComplete);
}
@Override
public void onReady() {
doSurroundOn(super::onReady);
}
void doSurroundOn(Runnable runnable) {
try (var ctc = CloseableThreadContext.put(GrpcUtil.KEY_REQUEST_ID, requestId)) {
runnable.run();
}
}
}
}
/*
* Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.token.common;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Optional;
import java.util.UUID;
import org.apache.logging.log4j.CloseableThreadContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.test.util.ReflectionTestUtils;
import de.ozgcloud.common.grpc.GrpcUtil;
import io.grpc.Metadata;
import io.grpc.ServerCall;
class CallContextGrpcServerInterceptorTest {
@InjectMocks
private CallContextGrpcServerInterceptor interceptor;
@Mock
private ServerCall.Listener<RequestTest> callListener;
@Mock
private Metadata metadata;
private CallContextGrpcServerInterceptor.LogContextSettingListener<RequestTest> listener;
@BeforeEach
void init() {
listener = spy(interceptor.new LogContextSettingListener(callListener, metadata));
}
@Nested
class TestGetRequestId {
@Test
void shouldCallGetRequestId() {
try (var grpcUtilMock = mockStatic(GrpcUtil.class)) {
listener.getRequestId(metadata);
grpcUtilMock.verify(() -> GrpcUtil.getRequestId(metadata));
}
}
@Test
void shouldGetRequestIdFromMetadata() {
try (var grpcUtilMock = mockStatic(GrpcUtil.class)) {
var requestId = UUID.randomUUID().toString();
grpcUtilMock.when(() -> GrpcUtil.getRequestId(any())).thenReturn(Optional.of(requestId));
var result = listener.getRequestId(metadata);
assertThat(result).isEqualTo(requestId);
}
}
@Test
void shouldReturnGeneratedId() {
try (var grpcUtilMock = mockStatic(GrpcUtil.class)) {
grpcUtilMock.when(() -> GrpcUtil.getRequestId(any())).thenReturn(Optional.empty());
var result = listener.getRequestId(metadata);
assertThat(result).isNotEmpty();
}
}
}
@Nested
class TestOnMessage {
@Test
void shouldCallDoSurroundOn() {
doNothing().when(listener).doSurroundOn(any());
listener.onMessage(new RequestTest());
verify(listener).doSurroundOn(any());
}
}
@Nested
class TestOnHalfClose {
@Test
void shouldCallDoSurroundOn() {
doNothing().when(listener).doSurroundOn(any());
listener.onHalfClose();
verify(listener).doSurroundOn(any());
}
}
@Nested
class TestOnCancel {
@Test
void shouldCallDoSurroundOn() {
doNothing().when(listener).doSurroundOn(any());
listener.onCancel();
verify(listener).doSurroundOn(any());
}
}
@Nested
class TestOnComplete {
@Test
void shouldCallDoSurroundOn() {
doNothing().when(listener).doSurroundOn(any());
listener.onComplete();
verify(listener).doSurroundOn(any());
}
}
@Nested
class TestOnReady {
@Test
void shouldCallDoSurroundOn() {
doNothing().when(listener).doSurroundOn(any());
listener.onReady();
verify(listener).doSurroundOn(any());
}
}
@Nested
class TestDoSurroundOn {
@Mock
private Runnable runnable;
private String requestId;
@BeforeEach
void init() {
requestId = (String) ReflectionTestUtils.getField(listener, "requestId");
}
@Test
void shouldSetThreadContext() {
try (var contextMock = mockStatic(CloseableThreadContext.class)) {
doSurroundOn();
contextMock.verify(() -> CloseableThreadContext.put(GrpcUtil.KEY_REQUEST_ID, requestId));
}
}
@Test
void shouldExecuteRunnable() {
doSurroundOn();
verify(runnable).run();
}
private void doSurroundOn() {
listener.doSurroundOn(runnable);
}
}
private record RequestTest() {
}
private record ResponseTest() {
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment