Repository navigation
feat: configure client-credentials token refresh timing #389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ea92495
5f0a0df
148f93c
adbecd7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,8 @@ public class OAuth2Client { | |||||
| private final CredentialsFlowRequest authRequest; | ||||||
| private final Configuration config; | ||||||
| private final Telemetry telemetry; | ||||||
| private final int tokenExpiryBufferSeconds; | ||||||
| private final int tokenExpiryJitterSeconds; | ||||||
|
Comment on lines
+24
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than separate fields, these could live in this.config (already this client's config holder). See my notes on the builder and the isValid calls here.
Suggested change
|
||||||
|
|
||||||
| /** | ||||||
| * Initializes a new instance of the {@link OAuth2Client} class | ||||||
|
|
@@ -29,6 +31,8 @@ public class OAuth2Client { | |||||
| */ | ||||||
| public OAuth2Client(Configuration configuration, ApiClient apiClient) throws FgaInvalidParameterException { | ||||||
| var clientCredentials = configuration.getCredentials().getClientCredentials(); | ||||||
| this.tokenExpiryBufferSeconds = configuration.getTokenExpiryBufferSeconds(); | ||||||
| this.tokenExpiryJitterSeconds = configuration.getTokenExpiryJitterSeconds(); | ||||||
|
Comment on lines
+34
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than separate fields, these could live in this.config (already this client's config holder). See my notes on the builder and the isValid calls here.
Suggested change
|
||||||
|
|
||||||
| this.apiClient = apiClient; | ||||||
| this.authRequest = | ||||||
|
|
@@ -54,15 +58,15 @@ public OAuth2Client(Configuration configuration, ApiClient apiClient) throws Fga | |||||
| public CompletableFuture<String> getAccessToken() throws FgaInvalidParameterException, ApiException { | ||||||
| // Fast path (lock-free): return cached token if still valid. | ||||||
| AccessToken current = snapshot.get(); | ||||||
| if (current.isValid()) { | ||||||
| if (current.isValid(tokenExpiryBufferSeconds, tokenExpiryJitterSeconds)) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Refer to my notes on this here. |
||||||
| return CompletableFuture.completedFuture(current.token()); | ||||||
| } | ||||||
|
|
||||||
| // Slow path: decide under the lock who starts the exchange. | ||||||
| synchronized (this) { | ||||||
| // Double-check: another thread may have refreshed while we waited. | ||||||
| AccessToken rechecked = snapshot.get(); | ||||||
| if (rechecked.isValid()) { | ||||||
| if (rechecked.isValid(tokenExpiryBufferSeconds, tokenExpiryJitterSeconds)) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Refer to my notes on this here. |
||||||
| return CompletableFuture.completedFuture(rechecked.token()); | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -420,8 +420,7 @@ public void applyAuthHeader(HttpRequest.Builder requestBuilder, Configuration co | |
| } | ||
|
|
||
| private OAuth2Client ensureOAuth2Client(Configuration configuration) throws FgaInvalidParameterException { | ||
| ClientCredentials cc = configuration.getCredentials().getClientCredentials(); | ||
| CredentialsCacheKey key = new CredentialsCacheKey(cc); | ||
| CredentialsCacheKey key = new CredentialsCacheKey(configuration); | ||
| OAuth2Client existing = oAuth2Clients.get(key); | ||
| if (existing != null) { | ||
| return existing; | ||
|
|
@@ -437,13 +436,18 @@ private static final class CredentialsCacheKey { | |
| private final String apiTokenIssuer; | ||
| private final String apiAudience; | ||
| private final String scopes; | ||
| private final int tokenExpiryBufferSeconds; | ||
| private final int tokenExpiryJitterSeconds; | ||
|
|
||
| CredentialsCacheKey(ClientCredentials cc) { | ||
| CredentialsCacheKey(Configuration configuration) { | ||
| ClientCredentials cc = configuration.getCredentials().getClientCredentials(); | ||
| this.clientId = cc.getClientId(); | ||
| this.clientSecretHash = sha256(cc.getClientSecret()); | ||
| this.apiTokenIssuer = cc.getApiTokenIssuer(); | ||
| this.apiAudience = cc.getApiAudience(); | ||
| this.scopes = cc.getScopes(); | ||
| this.tokenExpiryBufferSeconds = configuration.getTokenExpiryBufferSeconds(); | ||
| this.tokenExpiryJitterSeconds = configuration.getTokenExpiryJitterSeconds(); | ||
| } | ||
|
|
||
| private static byte[] sha256(String value) { | ||
|
|
@@ -463,12 +467,15 @@ public boolean equals(Object o) { | |
| && Arrays.equals(clientSecretHash, that.clientSecretHash) | ||
| && Objects.equals(apiTokenIssuer, that.apiTokenIssuer) | ||
| && Objects.equals(apiAudience, that.apiAudience) | ||
| && Objects.equals(scopes, that.scopes); | ||
| && Objects.equals(scopes, that.scopes) | ||
| && tokenExpiryBufferSeconds == that.tokenExpiryBufferSeconds | ||
| && tokenExpiryJitterSeconds == that.tokenExpiryJitterSeconds; | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| int result = Objects.hash(clientId, apiTokenIssuer, apiAudience, scopes); | ||
| int result = Objects.hash( | ||
| clientId, apiTokenIssuer, apiAudience, scopes, tokenExpiryBufferSeconds, tokenExpiryJitterSeconds); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My suggestion is to take the take buffer and jitter out of the cache key. All the changes in this file are just to add buffer/jitter to the cache key. So can we revet this file to main? The cache key identifies the token: same credentials -> same token. Buffer and jitter don't have any role in the token the IdP returns, they only affect when we refresh it. So putting them in the key just creates a second cache entry, and a second token exchange, for the same credentials whenever the timing differs. The key should only hold what determines the token. Nothing's lost (in terms of the functionality added in this PR) by removing them form the cache key: OAuth2Client reads the timing straight from the config when it's built, and ensureOAuth2Client already passes the config through. |
||
| result = 31 * result + Arrays.hashCode(clientSecretHash); | ||
| return result; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -36,6 +36,8 @@ public class Configuration implements BaseConfiguration { | |||||||||||||||||||||||||||||||||||||
| private Duration connectTimeout; | ||||||||||||||||||||||||||||||||||||||
| private int maxRetries; | ||||||||||||||||||||||||||||||||||||||
| private Duration minimumRetryDelay; | ||||||||||||||||||||||||||||||||||||||
| private int tokenExpiryBufferSeconds = FgaConstants.TOKEN_EXPIRY_THRESHOLD_BUFFER_IN_SEC; | ||||||||||||||||||||||||||||||||||||||
| private int tokenExpiryJitterSeconds = FgaConstants.TOKEN_EXPIRY_JITTER_IN_SEC; | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor consistency: Assign the defaults in the constructor - rather than inline initializers. |
||||||||||||||||||||||||||||||||||||||
| private Map<String, String> defaultHeaders; | ||||||||||||||||||||||||||||||||||||||
| private TelemetryConfiguration telemetryConfiguration; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
@@ -87,6 +89,8 @@ public Configuration override(ConfigurationOverride configurationOverride) { | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Credentials overrideCredentials = configurationOverride.getCredentials(); | ||||||||||||||||||||||||||||||||||||||
| result.credentials(overrideCredentials != null ? overrideCredentials : credentials); | ||||||||||||||||||||||||||||||||||||||
| result.tokenExpiryBufferSeconds(tokenExpiryBufferSeconds); | ||||||||||||||||||||||||||||||||||||||
| result.tokenExpiryJitterSeconds(tokenExpiryJitterSeconds); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+92
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two lines copy the client-level buffer/jitter as is, unlike every other field in override(). And buffer/jitter are the only two Configuration fields not mirrored in ConfigurationOverride. Here are my thoughts. I think the current approach is fine. I don't think these should be per-request overridable. The access token is shared per identity, so "use buffer=X for this one call" has no well-defined meaning on a shared token: whichever request happens to trigger the refresh wins, and it affects every other caller using that token. That's unlike credentials (overriding them yields a genuinely different token) or timeouts/headers (which apply only to your own request). There is a legitimate case where these values could differ: two credentials whose tokens have very different lifetimes (say 1h vs 5m), where a single buffer is wrong for one of them. But that is per-credential, not per-request, so the natural home would be on the Credentials object (the policy travels with the identity). I'd treat that as a follow-up, not part of this PR. So my suggestion: keep these as client-level only (don't add them to ConfigurationOverride), and add a short comment here noting that's intentional. @curfew-marathon, @SoulPancake - Does this make sense or is there something I'm missing? What do you suggest? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Without these two lines, override() would reset buffer/jitter to the 300 defaults on every request. Nothing in the test file covers that currently. Worth a small direct unit test in ConfigurationTest, in the override_* style, something like: That matches the existing override_apiUrl / override_userAgent tests. Feel free to also assert the original is unmodified if you want to mirror them fully. |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| String overrideUserAgent = configurationOverride.getUserAgent(); | ||||||||||||||||||||||||||||||||||||||
| result.userAgent(overrideUserAgent != null ? overrideUserAgent : userAgent); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -303,6 +307,41 @@ public Duration getMinimumRetryDelay() { | |||||||||||||||||||||||||||||||||||||
| return minimumRetryDelay; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Sets how many seconds before expiry a cached client-credentials token requires refresh. | ||||||||||||||||||||||||||||||||||||||
| * Defaults to 300. This is a client-level setting, preserved by request overrides. | ||||||||||||||||||||||||||||||||||||||
| * @throws IllegalArgumentException if seconds is negative. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+310
to
+314
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Small consistency nit: The setter has Also added a note to the reader so they know the behavior at the edges. |
||||||||||||||||||||||||||||||||||||||
| public Configuration tokenExpiryBufferSeconds(int seconds) { | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new buffer/jitter setters and getters don't have unit coverage in ConfigurationTest. Could you please add the relevant tests in ConfigurationTest file? For each of buffer and jitter: a valid set/read-back (with the assertSame chaining check), the zero case (0 is meaningful here, it disables the buffer / disables jitter), the negative-value IllegalArgumentException (like minimumRetryDelay_negativeDuration_throwsException), and a _hasDefaultValue test asserting 300. And others that I might have missed. You could refer to the minimumRetryDelay_* cluster and other test cases in the file for reference. |
||||||||||||||||||||||||||||||||||||||
| if (seconds < 0) { | ||||||||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("tokenExpiryBufferSeconds must be non-negative"); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| this.tokenExpiryBufferSeconds = seconds; | ||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| public int getTokenExpiryBufferSeconds() { | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Housekeeping nit: Could you add a short Javadoc with @return? Thank you. |
||||||||||||||||||||||||||||||||||||||
| return this.tokenExpiryBufferSeconds; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Sets the exclusive upper bound of additional random seconds subtracted on each token expiry check. | ||||||||||||||||||||||||||||||||||||||
| * Defaults to 300. Set to zero to disable jitter. This is a client-level setting, | ||||||||||||||||||||||||||||||||||||||
| * preserved by request overrides. | ||||||||||||||||||||||||||||||||||||||
| * @throws IllegalArgumentException if seconds is negative. | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Small consistency nit: The setter has Also added a note to the reader so they know the behavior at the edges. |
||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| public Configuration tokenExpiryJitterSeconds(int seconds) { | ||||||||||||||||||||||||||||||||||||||
| if (seconds < 0) { | ||||||||||||||||||||||||||||||||||||||
| throw new IllegalArgumentException("tokenExpiryJitterSeconds must be non-negative"); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| this.tokenExpiryJitterSeconds = seconds; | ||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| public int getTokenExpiryJitterSeconds() { | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Housekeeping nit: Could you add a short Javadoc with @return? Thank you. |
||||||||||||||||||||||||||||||||||||||
| return this.tokenExpiryJitterSeconds; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| public Configuration defaultHeaders(Map<String, String> defaultHeaders) { | ||||||||||||||||||||||||||||||||||||||
| this.defaultHeaders = defaultHeaders; | ||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
|
||
| import dev.openfga.sdk.api.configuration.Configuration; | ||
| import java.time.Instant; | ||
| import java.time.temporal.ChronoUnit; | ||
| import java.util.stream.Stream; | ||
|
|
@@ -40,6 +41,9 @@ private static Stream<Arguments> expTimeAndResults() { | |
| @ParameterizedTest(name = "{0}") | ||
| void testTokenValid(String name, Instant exp, boolean valid) { | ||
| AccessToken snapshot = new AccessToken("token", exp); | ||
| assertEquals(valid, snapshot.isValid()); | ||
| var defaults = new Configuration(); | ||
| assertEquals( | ||
| valid, | ||
| snapshot.isValid(defaults.getTokenExpiryBufferSeconds(), defaults.getTokenExpiryJitterSeconds())); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we move buffer and jitter into the @MethodSource args here instead of reading them off a default Configuration? Right now every case runs at 300/300, so isValid is only ever checked at the old default: the test would still pass even if it ignored the buffer/jitter arguments. With them as inputs we can add cases that prove it honors non-default values, e.g. a token expiring in 5 minutes being invalid at buffer=400 but valid at buffer=30. We don't need to use Configuration here either, this is a unit test of isValid, which just takes two ints, so coupling it to the config defaults isn't really relevant here. |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
| import dev.openfga.sdk.api.configuration.ApiToken; | ||
| import dev.openfga.sdk.api.configuration.ClientCredentials; | ||
| import dev.openfga.sdk.api.configuration.Configuration; | ||
| import dev.openfga.sdk.api.configuration.ConfigurationOverride; | ||
| import dev.openfga.sdk.api.configuration.Credentials; | ||
| import dev.openfga.sdk.constants.FgaConstants; | ||
| import dev.openfga.sdk.errors.ApiException; | ||
|
|
@@ -23,6 +24,8 @@ | |
| import java.util.List; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.CsvSource; | ||
| import org.mockito.ArgumentMatchers; | ||
| import org.mockito.Mockito; | ||
|
|
||
|
|
@@ -213,8 +216,9 @@ void clientCredentials_failureAsApiException() { | |
| requestBuilder.build().headers().firstValue("Authorization").isPresent()); | ||
| } | ||
|
|
||
| @Test | ||
| void clientCredentials_setsAuthHeader() throws Exception { | ||
| @ParameterizedTest | ||
| @CsvSource({"3600,300,300,1", "300,300,300,2", "300,30,0,1", "300,30,10,1", "20,30,0,2", "300,0,0,1"}) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: could the CsvSource get a header or a short comment on what each column drives (lifetime/buffer/jitter → expected exchanges)? Takes a second to work out why each row is 1 or 2 otherwise. Thank you. |
||
| void clientCredentials_setsAuthHeader(int lifetime, int buffer, int jitter, int exchanges) throws Exception { | ||
| String clientId = "some-client-id"; | ||
| String clientSecret = "some-client-secret"; | ||
| String apiAudience = "some-audience"; | ||
|
|
@@ -228,7 +232,9 @@ void clientCredentials_setsAuthHeader() throws Exception { | |
| containsString("client_secret=" + clientSecret), | ||
| containsString("audience=" + apiAudience), | ||
| containsString("grant_type=client_credentials"))) | ||
| .doReturn(200, String.format("{\"access_token\":\"%s\",\"expires_in\":3600}", exchangedToken)); | ||
| .doReturn( | ||
| 200, | ||
| String.format("{\"access_token\":\"%s\",\"expires_in\":%d}", exchangedToken, lifetime)); | ||
|
|
||
| HttpClient.Builder mockBuilder = mockHttpClientBuilder(mockHttpClient); | ||
| ApiClient apiClient = new ApiClient(mockBuilder); | ||
|
|
@@ -239,7 +245,9 @@ void clientCredentials_setsAuthHeader() throws Exception { | |
| .clientId(clientId) | ||
| .clientSecret(clientSecret) | ||
| .apiAudience(apiAudience) | ||
| .apiTokenIssuer(FgaConstants.TEST_ISSUER_URL))); | ||
| .apiTokenIssuer(FgaConstants.TEST_ISSUER_URL))) | ||
| .tokenExpiryBufferSeconds(buffer) | ||
| .tokenExpiryJitterSeconds(jitter); | ||
|
|
||
| HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().uri(URI.create(FgaConstants.TEST_API_URL)); | ||
| apiClient.applyAuthHeader(requestBuilder, configuration); | ||
|
|
@@ -248,17 +256,46 @@ void clientCredentials_setsAuthHeader() throws Exception { | |
| "Bearer " + exchangedToken, | ||
| requestBuilder.build().headers().firstValue("Authorization").orElseThrow()); | ||
|
|
||
| // A second call should reuse the cached token and not hit the issuer again. | ||
| // Reuse the token only while it is outside the configured refresh window. | ||
| HttpRequest.Builder secondBuilder = HttpRequest.newBuilder().uri(URI.create(FgaConstants.TEST_API_URL)); | ||
| apiClient.applyAuthHeader(secondBuilder, configuration); | ||
| apiClient.applyAuthHeader(secondBuilder, configuration.override(new ConfigurationOverride())); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any particular reason an override is used here? An empty one merges to the same config, so it reuses the same cache entry either way. |
||
| assertEquals( | ||
| "Bearer " + exchangedToken, | ||
| secondBuilder.build().headers().firstValue("Authorization").orElseThrow()); | ||
|
|
||
| mockHttpClient | ||
| .verify() | ||
| .post(String.format("%s/oauth/token", FgaConstants.TEST_ISSUER_URL)) | ||
| .called(1); | ||
| .called(exchanges); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @CsvSource({"31,10", "30,11"}) | ||
| void clientCredentials_differentRefreshSettings_useSeparateCaches(int buffer, int jitter) throws Exception { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This test exercises the behavior I flagged over on ApiClient.java (buffer/jitter in the cache key) here: same credentials with different timing producing separate token exchanges. It's tied to that discussion, so once we settle it there and take timing out of the key, this test should be deleted. Once we remove the above, it'd be worth replacing it with the inverse, a guard that same credentials share one cached client regardless of refresh timing, which is exactly the contract we'd be committing to. Sharing a skeleton below: |
||
| HttpClientMock mockHttpClient = new HttpClientMock(); | ||
| mockHttpClient | ||
| .onPost(FgaConstants.TEST_ISSUER_URL + "/oauth/token") | ||
| .doReturn(200, "{\"access_token\":\"token\",\"expires_in\":300}"); | ||
| ApiClient apiClient = new ApiClient(mockHttpClientBuilder(mockHttpClient)); | ||
| ClientCredentials credentials = new ClientCredentials() | ||
| .clientId("client") | ||
| .clientSecret("secret") | ||
| .apiTokenIssuer(FgaConstants.TEST_ISSUER_URL); | ||
| Configuration configuration = new Configuration() | ||
| .credentials(new Credentials(credentials)) | ||
| .tokenExpiryBufferSeconds(30) | ||
| .tokenExpiryJitterSeconds(10); | ||
|
|
||
| apiClient.applyAuthHeader(HttpRequest.newBuilder(), configuration); | ||
| apiClient.applyAuthHeader(HttpRequest.newBuilder(), configuration); | ||
| configuration.tokenExpiryBufferSeconds(buffer).tokenExpiryJitterSeconds(jitter); | ||
| apiClient.applyAuthHeader(HttpRequest.newBuilder(), configuration); | ||
| apiClient.applyAuthHeader(HttpRequest.newBuilder(), configuration); | ||
|
|
||
| mockHttpClient | ||
| .verify() | ||
| .post(FgaConstants.TEST_ISSUER_URL + "/oauth/token") | ||
| .called(2); | ||
| } | ||
|
|
||
| @Test | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SoulPancake - checking placement: README.md is autogenerated (it's in .openapi-generator/FILES, and CONTRIBUTING notes changes to those files go through sdk-generator), so as-is this edit will be overwritten on the next generation, right? Could you confirm whether these changes should go to sdk-generator instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it should be nice to have the changes in the generator to avoid drift later but it's okay for the maintainers to do that chore instead of the contributors.
So, non-blocking for the PR context.