Skip to content

Commit ef26ad2

Browse files
committed
test: added Historical Fee Estimation Endpoint test
Signed-off-by: kevkevinpal <oapallikunnel@gmail.com>
1 parent e9a50b9 commit ef26ad2

2 files changed

Lines changed: 185 additions & 0 deletions

File tree

app/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ dependencies {
5757
testImplementation(libs.ktor.client.core)
5858
testImplementation(libs.ktor.client.cio)
5959
testImplementation(libs.ktor.client.content.negotiation)
60+
61+
// MockK for mocking dependencies
62+
testImplementation("io.mockk:mockk:1.13.8")
63+
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0")
6064
}
6165

6266
// Apply a specific Java toolchain to ease working on different environments.
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright (c) 2025 Block, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package xyz.block.augurref.api
18+
19+
import com.fasterxml.jackson.databind.ObjectMapper
20+
import com.fasterxml.jackson.databind.SerializationFeature
21+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
22+
import com.fasterxml.jackson.module.kotlin.KotlinModule
23+
import com.fasterxml.jackson.module.kotlin.readValue
24+
import io.ktor.client.request.get
25+
import io.ktor.client.statement.bodyAsText
26+
import io.ktor.http.HttpStatusCode
27+
import io.ktor.serialization.jackson.jackson
28+
import io.ktor.server.application.install
29+
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
30+
import io.ktor.server.routing.routing
31+
import io.ktor.server.testing.TestApplicationBuilder
32+
import io.ktor.server.testing.testApplication
33+
import io.mockk.every
34+
import io.mockk.mockk
35+
import io.mockk.verify
36+
import org.junit.jupiter.api.Test
37+
import xyz.block.augur.FeeEstimate
38+
import xyz.block.augurref.service.MempoolCollector
39+
import java.time.Instant
40+
import kotlin.test.assertEquals
41+
import kotlin.test.assertTrue
42+
43+
class FeeEstimateEndpointTest {
44+
private val mockMempoolCollector = mockk<MempoolCollector>()
45+
private val objectMapper = ObjectMapper().apply {
46+
registerModule(KotlinModule.Builder().build())
47+
registerModule(JavaTimeModule())
48+
}
49+
50+
companion object {
51+
object TestData {
52+
object FeeRates {
53+
const val LOW_BLOCK1 = 10.5
54+
const val HIGH_BLOCK1 = 15.25
55+
const val LOW_BLOCK6 = 5.75
56+
const val HIGH_BLOCK6 = 8.1234
57+
}
58+
59+
object Probabilities {
60+
const val MEDIUM = 0.5
61+
const val HIGH = 0.9
62+
}
63+
64+
object BlockTargets {
65+
const val TARGET_1 = 1
66+
const val TARGET_6 = 6
67+
}
68+
}
69+
}
70+
71+
/**
72+
* Configures the test application with the same JSON serialization
73+
* settings as the main server and sets up the fees endpoint routing.
74+
*/
75+
private fun TestApplicationBuilder.configureTestApplication() {
76+
application {
77+
// Configure JSON serialization (same as main server)
78+
install(ContentNegotiation) {
79+
jackson {
80+
registerModule(JavaTimeModule())
81+
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
82+
enable(SerializationFeature.INDENT_OUTPUT)
83+
}
84+
}
85+
86+
routing {
87+
configureHistoricalFeesEndpoint(mockMempoolCollector)
88+
}
89+
}
90+
}
91+
92+
@Test
93+
fun `should return historical fee estimates when available`() = testApplication {
94+
val fixedInstant = Instant.parse("2025-01-15T10:00:00.123Z")
95+
val fixedInstantUnixTimestamp = fixedInstant.toEpochMilli()
96+
val mockFeeEstimate = createMockFeeEstimate(fixedInstant)
97+
every { mockMempoolCollector.getFeeEstimateForTimestamp(fixedInstantUnixTimestamp) } returns mockFeeEstimate
98+
99+
configureTestApplication()
100+
101+
client.get("/historical_fee?timestamp=$fixedInstantUnixTimestamp").apply {
102+
assertEquals(HttpStatusCode.OK, status)
103+
val responseBody = bodyAsText()
104+
val response: FeeEstimateResponse = objectMapper.readValue(responseBody)
105+
106+
assertEquals(fixedInstant, response.mempoolUpdateTime)
107+
assertEquals(2, response.estimates.size)
108+
assertTrue(response.estimates.containsKey("1"))
109+
assertTrue(response.estimates.containsKey("6"))
110+
111+
val firstBlock = response.estimates["1"]!!
112+
assertEquals(2, firstBlock.probabilities.size)
113+
assertEquals(TestData.FeeRates.LOW_BLOCK1, firstBlock.probabilities["0.50"]?.feeRate)
114+
assertEquals(TestData.FeeRates.HIGH_BLOCK1, firstBlock.probabilities["0.90"]?.feeRate)
115+
}
116+
117+
verify { mockMempoolCollector.getFeeEstimateForTimestamp(fixedInstantUnixTimestamp) }
118+
}
119+
120+
@Test
121+
fun `should return 503 when no historical estimates available`() = testApplication {
122+
val fixedInstant = Instant.parse("2025-01-15T10:00:00.123Z")
123+
val fixedInstantUnixTimestamp = fixedInstant.toEpochMilli()
124+
every { mockMempoolCollector.getFeeEstimateForTimestamp(fixedInstantUnixTimestamp) } returns null
125+
126+
configureTestApplication()
127+
128+
client.get("/historical_fee?timestamp=$fixedInstantUnixTimestamp").apply {
129+
assertEquals(HttpStatusCode.ServiceUnavailable, status)
130+
assertEquals("No historical fee estimates available for $fixedInstantUnixTimestamp", bodyAsText())
131+
}
132+
133+
verify { mockMempoolCollector.getFeeEstimateForTimestamp(fixedInstantUnixTimestamp) }
134+
}
135+
136+
@Test
137+
fun `should return 400 when timestamp param is malformed or null`() = testApplication {
138+
val fixedInstant = Instant.parse("2025-01-15T10:00:00.123Z")
139+
val fixedInstantUnixTimestamp = fixedInstant.toEpochMilli()
140+
every { mockMempoolCollector.getFeeEstimateForTimestamp(fixedInstantUnixTimestamp) } returns null
141+
142+
configureTestApplication()
143+
144+
client.get("/historical_fee?timestamp=$fixedInstant").apply {
145+
assertEquals(HttpStatusCode.BadRequest, status)
146+
assertEquals("Failed to parse timestamp, please input a unix timestamp", bodyAsText())
147+
}
148+
client.get("/historical_fee").apply {
149+
assertEquals(HttpStatusCode.BadRequest, status)
150+
assertEquals("timestamp parameter is required", bodyAsText())
151+
}
152+
}
153+
154+
private fun createMockFeeEstimate(timestamp: Instant): FeeEstimate {
155+
// Create mock data using the actual augur library structure
156+
val mockFeeEstimate = mockk<FeeEstimate>()
157+
158+
// Mock the properties that are accessed in the transformation
159+
every { mockFeeEstimate.timestamp } returns timestamp
160+
161+
// Create mock estimates map - this is what the transformation function expects
162+
val mockBlockTarget1 = mockk<xyz.block.augur.BlockTarget>()
163+
every { mockBlockTarget1.probabilities } returns mapOf(
164+
TestData.Probabilities.MEDIUM to TestData.FeeRates.LOW_BLOCK1,
165+
TestData.Probabilities.HIGH to TestData.FeeRates.HIGH_BLOCK1,
166+
)
167+
168+
val mockBlockTarget6 = mockk<xyz.block.augur.BlockTarget>()
169+
every { mockBlockTarget6.probabilities } returns mapOf(
170+
TestData.Probabilities.MEDIUM to TestData.FeeRates.LOW_BLOCK6,
171+
TestData.Probabilities.HIGH to TestData.FeeRates.HIGH_BLOCK6,
172+
)
173+
174+
every { mockFeeEstimate.estimates } returns mapOf(
175+
TestData.BlockTargets.TARGET_1 to mockBlockTarget1,
176+
TestData.BlockTargets.TARGET_6 to mockBlockTarget6,
177+
)
178+
179+
return mockFeeEstimate
180+
}
181+
}

0 commit comments

Comments
 (0)