-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHttp.kt
More file actions
103 lines (95 loc) · 3.71 KB
/
Http.kt
File metadata and controls
103 lines (95 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/*
* Copyright (c) 2023-2026 European Commission
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.europa.ec.eudi.openid4vci
import io.ktor.client.*
import io.ktor.client.engine.mock.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json
import java.net.URI
internal typealias HttpRequestDataMatcher = (HttpRequestData) -> Boolean
internal typealias HttpResponseDataBuilder = MockRequestHandleScope.(request: HttpRequestData?) -> HttpResponseData
/**
* Gets a [HttpRequestDataMatcher] that matches the provided [url] and [method].
*/
internal fun match(url: URI, method: HttpMethod = HttpMethod.Get): HttpRequestDataMatcher =
{ request -> request.url.toURI() == url && request.method == method }
internal fun endsWith(endsWith: String, method: HttpMethod = HttpMethod.Get): HttpRequestDataMatcher =
{ request -> request.url.encodedPath.endsWith(endsWith) && request.method == method }
/**
* Gets a [HttpResponseDataBuilder] that returns the provided [resource]
* as an 'application/json' [HttpResponseData] using the provided [status].
*/
internal fun jsonResponse(
resource: String,
acceptContentTypes: List<String> = listOf("application/json"),
status: HttpStatusCode = HttpStatusCode.OK,
): HttpResponseDataBuilder = {
respond(
content = getResourceAsText(resource),
status = status,
headers = headersOf(
*acceptContentTypes.map { HttpHeaders.ContentType to listOf(it) }.toTypedArray(),
),
)
}
/**
* A [requestMatcher] alongside the [responseBuilder] that must be invoked when it matches.
*/
internal data class RequestMocker(
val requestMatcher: HttpRequestDataMatcher,
val responseBuilder: HttpResponseDataBuilder,
val requestValidator: (request: HttpRequestData) -> Unit = {},
)
/**
* Factory method to create mocked http clients. Http clients behavior is based on the passed [requestMockers].
*
* Each [RequestMocker] is used exactly once, and then discarded. If multiple invocations of the same endpoint have to be mocked,
* multiple instances of the same [RequestMocker] must be provided.
*/
internal fun mockedHttpClient(
vararg requestMockers: RequestMocker,
expectSuccessOnly: Boolean = false,
): HttpClient {
val mutex = Mutex()
val mockers = requestMockers.toMutableList()
return HttpClient(MockEngine) {
engine {
addHandler { request ->
mutex.withLock {
val mocker = mockers.firstOrNull { it.requestMatcher(request) }
if (null != mocker) {
mockers.remove(mocker)
mocker.requestValidator(request)
mocker.responseBuilder(this, request)
} else {
respondError(HttpStatusCode.NotFound)
}
}
}
}
install(ContentNegotiation) {
json(
json = Json { ignoreUnknownKeys = true },
)
}
expectSuccess = expectSuccessOnly
}
}