1+ package dev .openfeature .contrib .providers .flagd .resolver .common ;
2+
3+ import dev .openfeature .contrib .providers .flagd .FlagdOptions ;
4+ import io .grpc .ManagedChannel ;
5+ import io .grpc .netty .GrpcSslContexts ;
6+ import io .grpc .netty .NettyChannelBuilder ;
7+ import io .netty .channel .epoll .Epoll ;
8+ import io .netty .channel .epoll .EpollDomainSocketChannel ;
9+ import io .netty .channel .epoll .EpollEventLoopGroup ;
10+ import io .netty .channel .unix .DomainSocketAddress ;
11+ import io .netty .handler .ssl .SslContextBuilder ;
12+ import org .junit .jupiter .api .Test ;
13+ import org .junit .jupiter .api .condition .EnabledOnOs ;
14+ import org .junit .jupiter .api .condition .OS ;
15+ import org .junit .jupiter .params .ParameterizedTest ;
16+ import org .junit .jupiter .params .provider .ValueSource ;
17+ import org .mockito .MockedStatic ;
18+
19+ import javax .net .ssl .SSLKeyException ;
20+ import java .io .File ;
21+ import java .util .concurrent .TimeUnit ;
22+
23+ import static org .assertj .core .api .Assertions .assertThat ;
24+ import static org .assertj .core .api .Assertions .assertThatThrownBy ;
25+ import static org .mockito .Mockito .any ;
26+ import static org .mockito .Mockito .anyLong ;
27+ import static org .mockito .Mockito .anyString ;
28+ import static org .mockito .Mockito .mock ;
29+ import static org .mockito .Mockito .mockStatic ;
30+ import static org .mockito .Mockito .verify ;
31+ import static org .mockito .Mockito .when ;
32+
33+ public class ChannelBuilderTest {
34+
35+ @ Test
36+ @ EnabledOnOs (OS .LINUX )
37+ void testNettyChannel_withSocketPath () {
38+ try (MockedStatic <Epoll > epollMock = mockStatic (Epoll .class );
39+ MockedStatic <NettyChannelBuilder > nettyMock = mockStatic (NettyChannelBuilder .class )) {
40+
41+ // Mocks
42+ epollMock .when (Epoll ::isAvailable ).thenReturn (true );
43+ NettyChannelBuilder mockBuilder = mock (NettyChannelBuilder .class );
44+ ManagedChannel mockChannel = mock (ManagedChannel .class );
45+
46+ nettyMock .when (() -> NettyChannelBuilder .forAddress (any (DomainSocketAddress .class )))
47+ .thenReturn (mockBuilder );
48+
49+ when (mockBuilder .keepAliveTime (anyLong (), any (TimeUnit .class ))).thenReturn (mockBuilder );
50+ when (mockBuilder .eventLoopGroup (any (EpollEventLoopGroup .class ))).thenReturn (mockBuilder );
51+ when (mockBuilder .channelType (EpollDomainSocketChannel .class )).thenReturn (mockBuilder );
52+ when (mockBuilder .usePlaintext ()).thenReturn (mockBuilder );
53+ when (mockBuilder .build ()).thenReturn (mockChannel );
54+
55+ // Input options
56+ FlagdOptions options = FlagdOptions .builder ()
57+ .socketPath ("/path/to/socket" )
58+ .keepAlive (1000 )
59+ .build ();
60+
61+ // Call method under test
62+ ManagedChannel channel = ChannelBuilder .nettyChannel (options );
63+
64+ // Assertions
65+ assertThat (channel ).isEqualTo (mockChannel );
66+ nettyMock .verify (() -> NettyChannelBuilder .forAddress (new DomainSocketAddress ("/path/to/socket" )));
67+ verify (mockBuilder ).keepAliveTime (1000 , TimeUnit .MILLISECONDS );
68+ verify (mockBuilder ).eventLoopGroup (any (EpollEventLoopGroup .class ));
69+ verify (mockBuilder ).channelType (EpollDomainSocketChannel .class );
70+ verify (mockBuilder ).usePlaintext ();
71+ verify (mockBuilder ).build ();
72+ }
73+ }
74+
75+ @ Test
76+ void testNettyChannel_withTlsAndCert () {
77+ try (MockedStatic <NettyChannelBuilder > nettyMock = mockStatic (NettyChannelBuilder .class )) {
78+ // Mocks
79+ NettyChannelBuilder mockBuilder = mock (NettyChannelBuilder .class );
80+ ManagedChannel mockChannel = mock (ManagedChannel .class );
81+ nettyMock .when (() -> NettyChannelBuilder .forTarget ("localhost:8080" ))
82+ .thenReturn (mockBuilder );
83+
84+ when (mockBuilder .keepAliveTime (anyLong (), any (TimeUnit .class ))).thenReturn (mockBuilder );
85+ when (mockBuilder .sslContext (any ())).thenReturn (mockBuilder );
86+ when (mockBuilder .build ()).thenReturn (mockChannel );
87+
88+ File mockCert = mock (File .class );
89+ when (mockCert .exists ()).thenReturn (true );
90+ String path = "test-harness/ssl/custom-root-cert.crt" ;
91+
92+ File file = new File (path );
93+ String absolutePath = file .getAbsolutePath ();
94+ // Input options
95+ FlagdOptions options = FlagdOptions .builder ()
96+ .host ("localhost" )
97+ .port (8080 )
98+ .keepAlive (5000 )
99+ .tls (true )
100+ .certPath (absolutePath )
101+ .build ();
102+
103+ // Call method under test
104+ ManagedChannel channel = ChannelBuilder .nettyChannel (options );
105+
106+ // Assertions
107+ assertThat (channel ).isEqualTo (mockChannel );
108+ nettyMock .verify (() -> NettyChannelBuilder .forTarget ("localhost:8080" ));
109+ verify (mockBuilder ).keepAliveTime (5000 , TimeUnit .MILLISECONDS );
110+ verify (mockBuilder ).sslContext (any ());
111+ verify (mockBuilder ).build ();
112+ }
113+ }
114+
115+ @ ParameterizedTest
116+ @ ValueSource (strings = {"/incorrect/{uri}/;)" })
117+ void testNettyChannel_withInvalidTargetUri (String uri ) {
118+ FlagdOptions options = FlagdOptions .builder ()
119+ .targetUri (uri )
120+ .build ();
121+
122+ assertThatThrownBy (() -> ChannelBuilder .nettyChannel (options ))
123+ .isInstanceOf (GenericConfigException .class )
124+ .hasMessageContaining ("Error with gRPC target string configuration" );
125+ }
126+
127+ @ Test
128+ void testNettyChannel_epollNotAvailable () {
129+ try (MockedStatic <Epoll > epollMock = mockStatic (Epoll .class )) {
130+ epollMock .when (Epoll ::isAvailable ).thenReturn (false );
131+
132+ FlagdOptions options = FlagdOptions .builder ().socketPath ("/path/to/socket" ).build ();
133+
134+ assertThatThrownBy (() -> ChannelBuilder .nettyChannel (options ))
135+ .isInstanceOf (IllegalStateException .class )
136+ .hasMessageContaining ("unix socket cannot be used" );
137+ }
138+ }
139+
140+ @ Test
141+ void testNettyChannel_sslException () throws Exception {
142+ try (MockedStatic <NettyChannelBuilder > nettyMock = mockStatic (NettyChannelBuilder .class )) {
143+ NettyChannelBuilder mockBuilder = mock (NettyChannelBuilder .class );
144+ nettyMock .when (() -> NettyChannelBuilder .forTarget (anyString ())).thenReturn (mockBuilder );
145+ try (MockedStatic <GrpcSslContexts > sslmock = mockStatic (GrpcSslContexts .class )) {
146+ SslContextBuilder sslMockBuilder = mock (SslContextBuilder .class );
147+ sslmock .when (GrpcSslContexts ::forClient ).thenReturn (sslMockBuilder );
148+ when (sslMockBuilder .build ()).thenThrow (new SSLKeyException ("Test SSL error" ));
149+ when (mockBuilder .keepAliveTime (anyLong (), any (TimeUnit .class ))).thenReturn (mockBuilder );
150+
151+ FlagdOptions options = FlagdOptions .builder ().tls (true ).build ();
152+
153+ assertThatThrownBy (() -> ChannelBuilder .nettyChannel (options ))
154+ .isInstanceOf (SslConfigException .class )
155+ .hasMessageContaining ("Error with SSL configuration" );
156+ }
157+ }
158+ }
159+ }
0 commit comments