Skip to content

Conversation

@TheLydonKing
Copy link
Collaborator

Release Notes:

  • Removed overloaded methods and introduced AuthMethod trait to indicate authentication method. This removes ambiguity with the default parameters and avoids compilation errors.

Closes: #154

@TheLydonKing TheLydonKing self-assigned this Dec 3, 2025
@TheLydonKing TheLydonKing requested a review from dk1844 as a code owner December 3, 2025 09:38
@TheLydonKing TheLydonKing added the enhancement New feature or request label Dec 3, 2025
@github-actions
Copy link

github-actions bot commented Dec 3, 2025

Report: Report: clientLibrary - scala:2.12.17

Metric (instruction) Coverage Threshold Status
Overall 62.51% 43.0%
Changed Files 78.28% 70.0%
File Path Coverage Threshold Status
AuthMethod.scala 100.0% 0.0%
TokenRetrievalClient.scala 56.56% 0.0%

Copy link
Collaborator

@dk1844 dk1844 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code is nice, but with a library we should meet additional standards (see comments below)

*/
def fetchRefreshToken(username: String, password: String): RefreshToken = {
fetchAccessAndRefreshToken(username, password, List.empty, false)._2
def fetchRefreshToken(authMethod: AuthMethod): RefreshToken = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither the new method nor the existing (which should be present for some time as described above) seem to be covered with unit tests.

That is unfortunate, because the appropriate class TokenRetrievalClientTest already exists and it would be nice to have it covered. I see two options:

  1. You may need to mock the creation of RestTemplate & KerberosRestTemplate (could be done by moving the creation to a method that you override in the test) to write the unit test properly.
  2. A half-measure would be just to mock the fetchToken methods in the class to check if those are receiving expected parameters. Not a complete unit test, but even this would be better than nothing if the option 1 cannot be reasonably done (I believe it should be).

@TheLydonKing TheLydonKing requested a review from dk1844 January 26, 2026 12:34
Comment on lines 23 to 74
class MockableTokenRetrievalClient(
host: String,
restTemplate: RestTemplate,
kerberosRestTemplate: KerberosRestTemplate) extends TokenRetrievalClient(host) {

override private[client] def fetchToken(issuerUri: String, username: String, password: String): String = {
val response: ResponseEntity[String] = restTemplate.exchange(
issuerUri,
HttpMethod.POST,
null,
classOf[String])
response.getBody
}

override private[client] def fetchToken(
issuerUri: String,
keyTabLocation: Option[String],
userPrincipal: Option[String]): String = {
val response: ResponseEntity[String] = kerberosRestTemplate.exchange(
issuerUri,
HttpMethod.POST,
null,
classOf[String])
response.getBody
}

override def refreshAccessToken(
accessToken: AccessToken,
refreshToken: RefreshToken): (AccessToken, RefreshToken) = {
val issuerUri = s"$host/token/refresh"
val jsonPayload: JsonObject = new JsonObject()
jsonPayload.addProperty("token", accessToken.token)
jsonPayload.addProperty("refresh", refreshToken.token)

val headers = new HttpHeaders()
headers.setContentType(MediaType.APPLICATION_JSON)
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON))

val requestEntity = new HttpEntity[String](jsonPayload.toString, headers)

val response: ResponseEntity[String] = restTemplate.exchange(
issuerUri,
HttpMethod.POST,
requestEntity,
classOf[String]
)
val jsonObject = JsonParser.parseString(response.getBody).getAsJsonObject
(
AccessToken(jsonObject.get("token").getAsString),
RefreshToken(jsonObject.get("refresh").getAsString)
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you are already providing the mocked exchange templates, I assume you want the full solution. Good choice.

However, you do not need to override all the fetchToken and refreshAccessToken (also these should be rather tested, not overridden). I think all you need is this:

Suggested change
class MockableTokenRetrievalClient(
host: String,
restTemplate: RestTemplate,
kerberosRestTemplate: KerberosRestTemplate) extends TokenRetrievalClient(host) {
override private[client] def fetchToken(issuerUri: String, username: String, password: String): String = {
val response: ResponseEntity[String] = restTemplate.exchange(
issuerUri,
HttpMethod.POST,
null,
classOf[String])
response.getBody
}
override private[client] def fetchToken(
issuerUri: String,
keyTabLocation: Option[String],
userPrincipal: Option[String]): String = {
val response: ResponseEntity[String] = kerberosRestTemplate.exchange(
issuerUri,
HttpMethod.POST,
null,
classOf[String])
response.getBody
}
override def refreshAccessToken(
accessToken: AccessToken,
refreshToken: RefreshToken): (AccessToken, RefreshToken) = {
val issuerUri = s"$host/token/refresh"
val jsonPayload: JsonObject = new JsonObject()
jsonPayload.addProperty("token", accessToken.token)
jsonPayload.addProperty("refresh", refreshToken.token)
val headers = new HttpHeaders()
headers.setContentType(MediaType.APPLICATION_JSON)
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON))
val requestEntity = new HttpEntity[String](jsonPayload.toString, headers)
val response: ResponseEntity[String] = restTemplate.exchange(
issuerUri,
HttpMethod.POST,
requestEntity,
classOf[String]
)
val jsonObject = JsonParser.parseString(response.getBody).getAsJsonObject
(
AccessToken(jsonObject.get("token").getAsString),
RefreshToken(jsonObject.get("refresh").getAsString)
)
}
class MockableTokenRetrievalClient(
host: String,
restTemplate: RestTemplate,
kerberosRestTemplate: KerberosRestTemplate) extends TokenRetrievalClient(host) {
override def createRestTemplate(): RestTemplate = restTemplate
override def createKerberosRestTemplate( /* opt params or two methods? */ ): KerberosRestTemplate = kerberosRestTempl

while updating the with

case class TokenRetrievalClient(host: String) {

  private[client] def createKerberosRestTemplate( /* opt params or two methods? */ ): KerberosRestTemplate = {
    new KerberosRestTemplate( /* opt params?*/)
  }

  private[client] def createRestTemplate(): RestTemplate = {
    new RestTemplate()
  }

// and wherever the `new RestTemplate()` or `new KerberosRestTemplate(...)` is used within `TokenRetrievalClient`, you replace it with the methods above.

This way, you only override the very minimal to get your exchange mocks in -- while the rest of the TokenRetrievalClient stays original as is correctly subjected to unit testing.

@TheLydonKing TheLydonKing requested a review from dk1844 February 4, 2026 09:39
Copy link
Collaborator

@dk1844 dk1844 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good advancement, just one more important thing in the tests:

when(mockRestTemplate.exchange(
      anyString(),
      any[HttpMethod],
      any[HttpEntity[String]],
      any[Class[String]]
    ))

is just too broad for a meaningful check. Please consider doing a better arguments check.

Comment on lines 46 to 48
case (Some(_), Some(_)) =>
logger.info(s"Fetching token with user $userPrincipal")
new KerberosRestTemplate(keyTabLocation.get, userPrincipal.get)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case (Some(_), Some(_)) =>
logger.info(s"Fetching token with user $userPrincipal")
new KerberosRestTemplate(keyTabLocation.get, userPrincipal.get)
case (Some(definedKeyTabLocation), Some(definedUserPrincipal)) =>
logger.info(s"Fetching token with user $definedUserPrincipal")
new KerberosRestTemplate(definedKeyTabLocation, definedUserPrincipal)

You do not have to resort to optionValueName.get this way 😄

Comment on lines 36 to 41
when(mockRestTemplate.exchange(
anyString(),
any[HttpMethod],
any[HttpEntity[String]],
any[Class[String]]
)).thenReturn(new ResponseEntity(tokenResponse, HttpStatus.OK))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, this is not good. This is the place where we express our expectations of what will be sent to the endpoint -- and the expectations must hold for the successful test (also for the test to make sense). If we allow anything, what is the point of the test -- it would just check that there was something sent (that is not enough).

import org.mockito.ArgumentMatchers.{eq => eqTo} // not to clash with `Matchers.eq`
// this may be common for the whole test or even with hardcoded values
val base64Credentials = new String(Base64.getEncoder.encodeToString((s"$dummyUser:$dummyPassword").getBytes()))
Suggested change
when(mockRestTemplate.exchange(
anyString(),
any[HttpMethod],
any[HttpEntity[String]],
any[Class[String]]
)).thenReturn(new ResponseEntity(tokenResponse, HttpStatus.OK))
val expectedBasicAuthHeader = new HttpHeaders()
expectedBasicAuthHeader.set("Authorization", s"Basic $base64Credentials")
when(mockRestTemplate.exchange(
eqTo(s"$dummyUri/token/generate"),
eqTo(HttpMethod.POST),
eqTo(new HttpEntity[String](null, expectedBasicAuthHeader)),
eqTo(classOf[String])
)).thenReturn(new ResponseEntity(tokenResponse, HttpStatus.OK))

... and I believe this is needed for every when(mockRestTemplate.exchange(...)) in this test -- of course the content will vary if it is a generate request, refresh request`, basic auth vs kerberos etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, my assumption here is that the Template in this case is mimicking the actual API response and the API is a separate entity from testing the Client entity itself, so irrelevant to testing the client.

I will change it accordingly though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request tested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Set Groups and Case sensitivity for token retrieval in client lib as optional

2 participants