44
55package kotlinx.rpc.grpc.test.proto
66
7+ import kotlinx.coroutines.CompletableDeferred
78import kotlinx.rpc.RpcServer
89import kotlinx.rpc.grpc.GrpcMetadata
910import kotlinx.rpc.grpc.Status
@@ -27,14 +28,27 @@ import kotlinx.rpc.grpc.test.assertGrpcFailure
2728import kotlinx.rpc.grpc.test.invoke
2829import kotlinx.rpc.registerService
2930import kotlinx.rpc.withService
31+ import kotlin.coroutines.cancellation.CancellationException
3032import kotlin.test.Test
3133import kotlin.test.assertEquals
34+ import kotlin.test.assertNotNull
35+ import kotlin.time.Duration.Companion.milliseconds
3236
3337class GrpcCallCredentialsTest : GrpcProtoTest () {
3438 override fun RpcServer.registerServices () {
3539 return registerService<EchoService > { EchoServiceImpl () }
3640 }
3741
42+ private fun assertAuthorizationHeaders (metadata : GrpcMetadata ? , vararg expectedTokens : String ) {
43+ assertNotNull(metadata, " Metadata should not be null" )
44+ val authHeaders = metadata.getAll(" authorization" )
45+ assertNotNull(authHeaders, " Authorization headers should not be null" )
46+ assertEquals(expectedTokens.size, authHeaders.size)
47+ expectedTokens.forEachIndexed { index, token ->
48+ assertEquals(token, authHeaders[index])
49+ }
50+ }
51+
3852 @Test
3953 fun `test simple combined call credentials - should succeed` () {
4054 var grpcMetadata: GrpcMetadata ? = null
@@ -49,9 +63,7 @@ class GrpcCallCredentialsTest : GrpcProtoTest() {
4963 test = ::unaryCall
5064 )
5165
52- val authHeaders = grpcMetadata?.getAll(" authorization" )
53- assertEquals(1 , authHeaders?.size)
54- assertEquals(" Bearer token" , authHeaders?.single())
66+ assertAuthorizationHeaders(grpcMetadata, " Bearer token" )
5567 }
5668
5769 @Test
@@ -68,10 +80,31 @@ class GrpcCallCredentialsTest : GrpcProtoTest() {
6880 },
6981 test = ::unaryCall
7082 )
71- val authHeaders = grpcMetadata?.getAll(" authorization" )
72- assertEquals(2 , authHeaders?.size)
73- assertEquals(" Bearer token-1" , authHeaders?.first())
74- assertEquals(" Bearer token-2" , authHeaders?.get(1 ))
83+
84+ assertAuthorizationHeaders(grpcMetadata, " Bearer token-1" , " Bearer token-2" )
85+ }
86+
87+ @Test
88+ fun `test combine three or more call credentials at config and interceptor time - should succeed` () {
89+ var grpcMetadata: GrpcMetadata ? = null
90+ val configCallCreds = (NoTLSBearerTokenCredentials (" token-1" ) + NoTLSBearerTokenCredentials (" token-2" ) + NoTLSBearerTokenCredentials (" token-3" ))
91+ runGrpcTest(
92+ configure = {
93+ credentials = plaintext() + configCallCreds
94+ },
95+ clientInterceptors = clientInterceptor {
96+ callOptions.callCredentials + = NoTLSBearerTokenCredentials (" token-4" )
97+ proceed(it)
98+ },
99+ serverInterceptors = serverInterceptor {
100+ grpcMetadata = requestHeaders
101+ proceed(it)
102+ },
103+ test = ::unaryCall
104+ )
105+
106+ // 4 before 1 as callOption callCredentials are applied before client level ones
107+ assertAuthorizationHeaders(grpcMetadata, " Bearer token-4" , " Bearer token-1" , " Bearer token-2" , " Bearer token-3" )
75108 }
76109
77110 @Test
@@ -106,52 +139,27 @@ class GrpcCallCredentialsTest : GrpcProtoTest() {
106139 test = ::unaryCall
107140 )
108141
109- val authHeaders = grpcMetadata?.getAll(" authorization" )
110- assertEquals(1 , authHeaders?.size)
111- assertEquals(" Bearer token" , authHeaders?.single())
142+ assertAuthorizationHeaders(grpcMetadata, " Bearer token" )
112143 }
113144
114145 @Test
115146 fun `test throw status exception - should fail with status` () {
116- val throwingCallCredentials = object : GrpcCallCredentials {
117- override suspend fun Context.getRequestMetadata (): GrpcMetadata {
118- throw StatusException (Status (StatusCode .UNIMPLEMENTED , " This is my custom exception" ))
119- }
120-
121- override val requiresTransportSecurity: Boolean
122- get() = false
123- }
124-
125147 assertGrpcFailure(StatusCode .UNAVAILABLE , " This is my custom exception" ) {
126148 runGrpcTest(
127149 configure = {
128- credentials = plaintext() + throwingCallCredentials
129- },
130- serverInterceptors = serverInterceptor {
131- proceed(it)
150+ credentials = plaintext() + ThrowingCallCredentials ()
132151 },
133152 test = ::unaryCall
134153 )
135154 }
136155 }
137156
138-
139157 @Test
140158 fun `test throw exception - should fail` () {
141- val throwingCallCredentials = object : GrpcCallCredentials {
142- override suspend fun Context.getRequestMetadata (): GrpcMetadata {
143- throw IllegalStateException (" This is my custom exception" )
144- }
145- override val requiresTransportSecurity: Boolean
146- get() = false
147- }
148159 assertGrpcFailure(StatusCode .UNAVAILABLE , " This is my custom exception" ) {
149160 runGrpcTest(
150161 configure = {
151- credentials = plaintext() + throwingCallCredentials
152- },
153- serverInterceptors = serverInterceptor {
154- proceed(it)
162+ credentials = plaintext() + ThrowingCallCredentials (IllegalStateException (" This is my custom exception" ))
155163 },
156164 test = ::unaryCall
157165 )
@@ -172,9 +180,8 @@ class GrpcCallCredentialsTest : GrpcProtoTest() {
172180 },
173181 test = ::unaryCall
174182 )
175- val authHeaders = grpcMetadata?.getAll(" authorization" )
176- assertEquals(1 , authHeaders?.size)
177- assertEquals(" Bearer token" , authHeaders?.single())
183+
184+ assertAuthorizationHeaders(grpcMetadata, " Bearer token" )
178185 }
179186
180187 @Test
@@ -236,51 +243,87 @@ class GrpcCallCredentialsTest : GrpcProtoTest() {
236243 assertEquals(" test.example.com" , capturedAuthority)
237244 }
238245
239- // @Test
240- // fun `test long running call credentials - should succeed`() {
241- // var grpcMetadata: GrpcMetadata? = null
242- // class SlowCredentials(
243- // val token: String
244- // ) : GrpcCallCredentials {
245- // override suspend fun Context.getRequestMetadata(): GrpcMetadata {
246- // delay(1000)
247- // return buildGrpcMetadata {
248- // append(token, token)
249- // }
250- // }
251- //
252- // override val requiresTransportSecurity: Boolean
253- // get() = false
254- // }
255- //
256- // runGrpcTest(
257- // configure = {
258- // credentials = plaintext() + SlowCredentials("token-1")
259- // },
260- // clientInterceptors = clientInterceptor {
261- // callOptions.callCredentials += SlowCredentials("token-2")
262- // proceed(it)
263- // },
264- // serverInterceptors = serverInterceptor {
265- // grpcMetadata = requestHeaders
266- // proceed(it)
267- // },
268- // test = {
269- // coroutineScope {
270- // launch { unaryCall(it) }
271- // delay(200)
272- // cancel("Midcanceling")
273- // }
274- // }
275- // )
276- //
277- // val authHeaders = grpcMetadata?.getAll("token-1")
278- // assertEquals(1, authHeaders?.size)
279- // assertEquals("token-1", authHeaders?.single())
280- // val authHeaders2 = grpcMetadata?.getAll("token-2")
281- // assertEquals(1, authHeaders2?.size)
282- // assertEquals("token-2", authHeaders2?.single())
283- // }
246+ @Test
247+ fun `test call credentials cancellation because of timeout - should fail` () {
248+ var callCredsCancelled = false
249+ val slowCredentials = object : GrpcCallCredentials {
250+ override suspend fun Context.getRequestMetadata (): GrpcMetadata {
251+ try {
252+ // block indefinitely to simulate a slow call credential.
253+ // this works even in a runTest coroutine dispatcher
254+ CompletableDeferred <Unit >().await()
255+ return buildGrpcMetadata {
256+ append(" Authentication" , " Bearer token" )
257+ }
258+ } catch (err: CancellationException ) {
259+ callCredsCancelled = true
260+ throw err
261+ }
262+ }
263+
264+ override val requiresTransportSecurity: Boolean
265+ get() = false
266+ }
267+ assertGrpcFailure(StatusCode .DEADLINE_EXCEEDED ) {
268+ runGrpcTest(
269+ configure = {
270+ credentials = plaintext() + slowCredentials
271+ },
272+ clientInterceptors = clientInterceptor {
273+ callOptions.timeout = 100 .milliseconds
274+ proceed(it)
275+ },
276+ test = ::unaryCall
277+ )
278+ }
279+
280+ // assert that the getRequestMetadata suspend method was cancelled
281+ assertEquals(true , callCredsCancelled)
282+ }
283+
284+ @Test
285+ fun `test call credentials should be called even if second fails - should fail` () {
286+ var calledCredentialHandler = false
287+ val someCredentials = object : PlaintextCallCredentials () {
288+ override suspend fun Context.getRequestMetadata (): GrpcMetadata {
289+ calledCredentialHandler = true
290+ return buildGrpcMetadata { }
291+ }
292+ }
293+ assertGrpcFailure(StatusCode .UNAVAILABLE ) {
294+ runGrpcTest(
295+ configure = {
296+ credentials = plaintext() + someCredentials + ThrowingCallCredentials ()
297+ },
298+ test = ::unaryCall
299+ )
300+ }
301+
302+ // assert that the getRequestMetadata suspend method was cancelled
303+ assertEquals(true , calledCredentialHandler)
304+ }
305+
306+ @Test
307+ fun `test call credentials should not be called if previous one fails - should fail` () {
308+ var calledCredentialHandler = false
309+ val someCredentials = object : PlaintextCallCredentials () {
310+ override suspend fun Context.getRequestMetadata (): GrpcMetadata {
311+ calledCredentialHandler = true
312+ return buildGrpcMetadata { }
313+ }
314+ }
315+ assertGrpcFailure(StatusCode .UNAVAILABLE ) {
316+ runGrpcTest(
317+ configure = {
318+ credentials = plaintext() + ThrowingCallCredentials () + someCredentials
319+ },
320+ test = ::unaryCall
321+ )
322+ }
323+
324+ // assert that the getRequestMetadata suspend method was cancelled
325+ assertEquals(false , calledCredentialHandler)
326+ }
284327}
285328
286329private suspend fun unaryCall (grpcClient : GrpcClient ) {
@@ -289,21 +332,31 @@ private suspend fun unaryCall(grpcClient: GrpcClient) {
289332 assertEquals(" Echo" , response.message)
290333}
291334
335+ abstract class PlaintextCallCredentials : GrpcCallCredentials {
336+ override val requiresTransportSecurity: Boolean
337+ get() = false
338+ }
339+
340+ class ThrowingCallCredentials (
341+ private val exception : Throwable = StatusException (Status (StatusCode .UNIMPLEMENTED , "This is my custom exception"))
342+ ) : PlaintextCallCredentials() {
343+ override suspend fun Context.getRequestMetadata (): GrpcMetadata {
344+ throw exception
345+ }
346+ }
347+
292348class NoTLSBearerTokenCredentials (
293349 val token : String = " token"
294- ): GrpcCallCredentials {
350+ ) : PlaintextCallCredentials() {
295351 override suspend fun Context.getRequestMetadata (): GrpcMetadata {
296352 return buildGrpcMetadata {
297353 // potentially fetching the token from a secure storage
298354 append(" Authorization" , " Bearer $token " )
299355 }
300356 }
301-
302- override val requiresTransportSecurity: Boolean
303- get() = false
304357}
305358
306- class TlsBearerTokenCredentials : GrpcCallCredentials {
359+ class TlsBearerTokenCredentials : GrpcCallCredentials {
307360 override suspend fun Context.getRequestMetadata (): GrpcMetadata {
308361 return buildGrpcMetadata {
309362 append(" Authorization" , " Bearer token" )
0 commit comments