-
Notifications
You must be signed in to change notification settings - Fork 7
/
TestBase.kt
176 lines (150 loc) · 5.96 KB
/
TestBase.kt
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package blue.mild.covid.vaxx.utils
import blue.mild.covid.vaxx.dao.model.DatabaseSetup
import blue.mild.covid.vaxx.dto.config.DatabaseConfigurationDto
import blue.mild.covid.vaxx.dto.config.RateLimitConfigurationDto
import blue.mild.covid.vaxx.jobs.registerPeriodicJobs
import blue.mild.covid.vaxx.security.auth.JwtService
import blue.mild.covid.vaxx.security.auth.UserPrincipal
import blue.mild.covid.vaxx.security.auth.registerJwtAuth
import blue.mild.covid.vaxx.setup.bindConfiguration
import blue.mild.covid.vaxx.setup.registerClasses
import blue.mild.covid.vaxx.setup.setupDiAwareApplication
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.TestApplicationCall
import io.ktor.server.testing.TestApplicationEngine
import io.ktor.server.testing.TestApplicationRequest
import io.ktor.server.testing.setBody
import io.ktor.server.testing.withTestApplication
import mu.KLogging
import org.flywaydb.core.Flyway
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.instance
import org.kodein.di.ktor.closestDI
import org.kodein.di.ktor.di
import org.kodein.di.singleton
import java.time.Duration
import java.time.LocalDate
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
/**
* Test base that has access to initialized dependency injection.
*
* Use [overrideDIContainer] to inject and override additional dependencies.
*/
open class DiAwareTestBase {
protected val rootDI = DI(allowSilentOverride = true) {
bindConfiguration()
registerJwtAuth()
registerClasses()
registerPeriodicJobs()
// disable migration on startup, instead migrate during tests
bind<Boolean>("should-migrate") with singleton { false }
// disable rate limiting
bind<RateLimitConfigurationDto>() with singleton {
RateLimitConfigurationDto(
enableRateLimiting = false,
rateLimit = 0,
rateLimitDuration = Duration.ofMinutes(0L)
)
}
bind<Flyway>() with singleton {
val dbConfig = instance<DatabaseConfigurationDto>()
Flyway
.configure()
.cleanDisabled(false)
.dataSource(dbConfig.url, dbConfig.userName, dbConfig.password)
.load()
}
overrideDIContainer()?.let { extend(it, allowOverride = true) }
}
/**
* Override this if you want to add additional bindings or if you want to override
* some instances from the base DI container.
*/
protected open fun overrideDIContainer(): DI? = null
}
/**
* Base class that has access to the database.
*
* The database is cleaned and migrated before each test.
*/
open class DatabaseTestBase(private val shouldSetupDatabase: Boolean = true) : DiAwareTestBase() {
private val flyway by rootDI.instance<Flyway>()
@BeforeEach
fun beforeEach() {
if (shouldSetupDatabase) {
val dbConfig by rootDI.instance<DatabaseConfigurationDto>()
DatabaseSetup.connect(dbConfig)
require(DatabaseSetup.isConnected()) { "It was not possible to connect to db database!" }
flyway.clean()
flyway.migrate()
populateDatabase(rootDI)
}
}
@AfterEach
fun afterEach() {
if (shouldSetupDatabase) {
flyway.clean()
}
}
/**
* Override this when you need to add additional data before the each test.
* This method is called when the database is fully migrated.
*
* Executed only when [shouldSetupDatabase] is true.
*/
protected open fun populateDatabase(di: DI) {}
}
/**
* Base class with access to running Ktor server. Parameter needsDatabase indicates
* whether the test class needs access to the database or not.
*
* Use [withTestApplication] to access the server resource.
*
* Examples available: [here](https://github.com/ktorio/ktor-documentation/tree/master/codeSnippets/snippets/testable)
*/
open class ServerTestBase(needsDatabase: Boolean = true) : DatabaseTestBase(needsDatabase) {
protected companion object : KLogging()
protected val mapper by rootDI.instance<ObjectMapper>()
protected val jwtService by rootDI.instance<JwtService>()
protected fun <R> withTestApplication(test: TestApplicationEngine.() -> R) {
withTestApplication(
{
di { extend(rootDI, allowOverride = true) }
setupDiAwareApplication()
},
test
)
}
protected fun TestApplicationEngine.closestDI() = application.closestDI()
protected fun TestApplicationCall.expectStatus(status: HttpStatusCode) = assertEquals(status, response.status())
protected inline fun <reified T> TestApplicationRequest.jsonBody(data: T) {
setBody(mapper.writeValueAsString(data))
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
}
protected inline fun <reified T> TestApplicationCall.receive(): T =
assertNotNull(receiveOrNull<T>(), "Received content was null!")
protected inline fun <reified T> TestApplicationCall.receiveOrNull(): T? {
val content = response.content ?: return null
logger.info { "received content:\n$content" }
return mapper.readValue(content)
}
protected val defaultPrincipal = UserPrincipal(
userId = DatabaseData.admin.id,
userRole = DatabaseData.admin.role,
vaccineSerialNumber = "some data",
vaccineExpiration = LocalDate.now(),
nurseId = DatabaseData.nurses.first().id
)
protected fun TestApplicationRequest.authorize(principal: UserPrincipal = defaultPrincipal) {
val token = jwtService.generateToken(principal).token
addHeader(HttpHeaders.Authorization, "Bearer $token")
}
}