Skip to content

Commit

Permalink
Remove Mockito usage from Container Registry (Azure#42464)
Browse files Browse the repository at this point in the history
  • Loading branch information
alzimmermsft authored Oct 18, 2024
1 parent 9240bbd commit c5b98ab
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 191 deletions.
59 changes: 0 additions & 59 deletions sdk/containerregistry/azure-containers-containerregistry/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,37 +58,11 @@
<artifactId>azure-core-http-netty</artifactId>
<version>1.15.5</version> <!-- {x-version-update;com.azure:azure-core-http-netty;dependency} -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.11.0</version> <!-- {x-version-update;org.mockito:mockito-core;external_dependency} -->
<scope>test</scope>
</dependency>
<!-- bytebuddy dependencies are required for mockito 4.11.0 to work with Java 21. Mockito 4.11.0 is the last release -->
<!-- of Mockito supporting Java 8 as a baseline. -->
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.15.5</version> <!-- {x-version-update;testdep_net.bytebuddy:byte-buddy;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.15.5</version> <!-- {x-version-update;testdep_net.bytebuddy:byte-buddy-agent;external_dependency} -->
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core-test</artifactId>
<version>1.27.0-beta.2</version> <!-- {x-version-update;com.azure:azure-core-test;dependency} -->
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.azure</groupId>
<artifactId>azure-core-http-jdk-httpclient</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.azure.resourcemanager</groupId>
Expand All @@ -103,37 +77,4 @@
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>java9-plus</id>
<activation>
<jdk>[9,)</jdk>
</activation>
<build>
<testResources>
<testResource>
<directory>src/samples/resources</directory>
</testResource>
<testResource>
<directory>src/test/resources</directory>
</testResource>
</testResources>
</build>
</profile>

<profile>
<id>java12plus</id>
<activation>
<jdk>[12,)</jdk>
</activation>
<dependencies>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core-http-jdk-httpclient</artifactId>
<version>1.0.0-beta.17</version> <!-- {x-version-update;com.azure:azure-core-http-jdk-httpclient;dependency} -->
<scope>test</scope>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@
* <p>Step5: GET /api/v1/acr/repositories
* Request Header: {Bearer acrTokenAccess}</p>
*/
public final class ContainerRegistryCredentialsPolicy extends BearerTokenAuthenticationPolicy {
public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
public static final String SCOPES_PARAMETER = "scope";
public static final String SERVICE_PARAMETER = "service";
public class ContainerRegistryCredentialsPolicy extends BearerTokenAuthenticationPolicy {
private static final String SCOPES_PARAMETER = "scope";
private static final String SERVICE_PARAMETER = "service";
private final ContainerRegistryTokenService acrCredential;

/**
Expand Down Expand Up @@ -68,7 +67,7 @@ public Mono<Void> authorizeRequest(HttpPipelineCallContext context) {
*/
@Override
public Mono<Boolean> authorizeRequestOnChallenge(HttpPipelineCallContext context, HttpResponse response) {
String authHeader = response.getHeaderValue(WWW_AUTHENTICATE);
String authHeader = response.getHeaderValue(HttpHeaderName.WWW_AUTHENTICATE);
if (!(response.getStatusCode() == 401 && authHeader != null)) {
return Mono.just(false);
} else {
Expand Down Expand Up @@ -108,7 +107,7 @@ public void authorizeRequestSync(HttpPipelineCallContext context) {
*/
@Override
public boolean authorizeRequestOnChallengeSync(HttpPipelineCallContext context, HttpResponse response) {
String authHeader = response.getHeaderValue(WWW_AUTHENTICATE);
String authHeader = response.getHeaderValue(HttpHeaderName.WWW_AUTHENTICATE);
if (!(response.getStatusCode() == 401 && authHeader != null)) {
return false;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,173 +3,193 @@

package com.azure.containers.containerregistry;

import com.azure.containers.containerregistry.implementation.AzureContainerRegistryImpl;
import com.azure.containers.containerregistry.implementation.authentication.ContainerRegistryCredentialsPolicy;
import com.azure.containers.containerregistry.implementation.authentication.ContainerRegistryTokenRequestContext;
import com.azure.containers.containerregistry.implementation.authentication.ContainerRegistryTokenService;
import com.azure.core.credential.AccessToken;
import com.azure.core.credential.TokenRequestContext;
import com.azure.core.http.HttpHeaderName;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpMethod;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.HttpPipelineBuilder;
import com.azure.core.http.HttpPipelineCallContext;
import com.azure.core.http.HttpPipelineNextPolicy;
import com.azure.core.http.HttpPipelineNextSyncPolicy;
import com.azure.core.http.HttpRequest;
import com.azure.core.http.HttpResponse;
import com.azure.core.test.SyncAsyncExtension;
import com.azure.core.test.annotation.SyncAsyncTest;
import com.azure.core.test.http.MockHttpResponse;
import com.azure.core.util.Context;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.time.OffsetDateTime;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class ContainerRegistryCredentialPolicyTests {

public static final String AUTHENTICATE_HEADER = "Bearer realm=\"https://mytest.azurecr.io/oauth2/token\",service=\"mytest.azurecr.io\",scope=\"registry:catalog:*\",error=\"invalid_token\"";
public static final String AUTHENTICATE_HEADER
= "Bearer realm=\"https://mytest.azurecr.io/oauth2/token\",service=\"mytest.azurecr.io\",scope=\"registry:catalog:*\",error=\"invalid_token\"";
public static final Integer UNAUTHORIZED = 401;
public static final Integer SUCCESS = 200;
public static final String BEARER = "Bearer";
public static final String SERVICENAME = "mytest.azurecr.io";
public static final String SCOPENAME = "registry:catalog:*";
private static final HttpRequest REQUEST = new HttpRequest(HttpMethod.GET, "https://mytest.azurecr.io");

private ContainerRegistryTokenService service;
private HttpResponse unauthorizedHttpResponse;
private HttpResponse unauthorizedHttpResponseWithoutHeader;
private HttpPipelineCallContext callContext;
private HttpResponse successResponse;
private HttpPipelineNextPolicy nextPolicy;

private HttpPipelineNextSyncPolicy nextSyncPolicy;
@BeforeEach
public void setup() {
AccessToken accessToken = new AccessToken("tokenValue", OffsetDateTime.now().plusMinutes(30));

ContainerRegistryTokenService mockService = mock(ContainerRegistryTokenService.class);
when(mockService.getToken(any(ContainerRegistryTokenRequestContext.class))).thenReturn(Mono.just(accessToken));
when(mockService.getTokenSync(any(ContainerRegistryTokenRequestContext.class))).thenReturn(accessToken);

HttpRequest request = new HttpRequest(HttpMethod.GET, "https://mytest.azurecr.io");

HttpPipelineCallContext context = mock(HttpPipelineCallContext.class);
when(context.getHttpRequest()).thenReturn(request);

MockHttpResponse unauthorizedResponseWithHeader = new MockHttpResponse(request, UNAUTHORIZED,
ContainerRegistryTokenService mockService = new ContainerRegistryTokenService(null, null,
new AzureContainerRegistryImpl("https://azure.com",
ContainerRegistryServiceVersion.getLatest().toString())) {
@Override
public Mono<AccessToken> getToken(TokenRequestContext request) {
return Mono.just(accessToken);
}

@Override
public AccessToken getTokenSync(TokenRequestContext tokenRequestContext) {
return accessToken;
}
};

AtomicReference<HttpPipelineCallContext> contextReference = new AtomicReference<>();
new HttpPipelineBuilder().policies((httpPipelineCallContext, httpPipelineNextPolicy) -> {
contextReference.set(httpPipelineCallContext);
return Mono.empty();
}).httpClient(ignored -> Mono.empty()).build().sendSync(REQUEST, Context.NONE);

MockHttpResponse unauthorizedResponseWithHeader = new MockHttpResponse(REQUEST, UNAUTHORIZED,
new HttpHeaders().set(HttpHeaderName.WWW_AUTHENTICATE, AUTHENTICATE_HEADER));

MockHttpResponse unauthorizedResponseWithoutHeader = new MockHttpResponse(request, UNAUTHORIZED);
MockHttpResponse unauthorizedResponseWithoutHeader = new MockHttpResponse(REQUEST, UNAUTHORIZED);

MockHttpResponse successResponse = new MockHttpResponse(request, SUCCESS);

HttpPipelineNextPolicy mockNextClone = mock(HttpPipelineNextPolicy.class);
when(mockNextClone.process()).thenReturn(Mono.just(successResponse));
HttpPipelineNextPolicy mockNext = mock(HttpPipelineNextPolicy.class);
when(mockNext.clone()).thenReturn(mockNextClone);
when(mockNext.process()).thenReturn(Mono.just(unauthorizedResponseWithHeader));

HttpPipelineNextSyncPolicy mockNextSyncClone = mock(HttpPipelineNextSyncPolicy.class);
when(mockNextSyncClone.processSync()).thenReturn(successResponse);
HttpPipelineNextSyncPolicy mockNextSync = mock(HttpPipelineNextSyncPolicy.class);
when(mockNextSync.clone()).thenReturn(mockNextSyncClone);
when(mockNextSync.processSync()).thenReturn(unauthorizedResponseWithHeader);
MockHttpResponse successResponse = new MockHttpResponse(REQUEST, SUCCESS);

this.service = mockService;
this.unauthorizedHttpResponse = unauthorizedResponseWithHeader;
this.unauthorizedHttpResponseWithoutHeader = unauthorizedResponseWithoutHeader;
this.callContext = context;
this.callContext = contextReference.get();
this.successResponse = successResponse;
this.nextPolicy = mockNext;
this.nextSyncPolicy = mockNextSync;

}

@SyncAsyncTest
public void requestNoRetryOnOtherErrorCodes() {
ContainerRegistryCredentialsPolicy policy = new ContainerRegistryCredentialsPolicy(this.service, "foo");
ContainerRegistryCredentialsPolicy spyPolicy = Mockito.spy(policy);

when(nextPolicy.process()).thenReturn(Mono.just(successResponse));
when(nextSyncPolicy.processSync()).thenReturn(successResponse);

SyncAsyncExtension.execute(
() -> policy.processSync(this.callContext, this.nextSyncPolicy),
() -> policy.process(this.callContext, this.nextPolicy)
);
AtomicInteger syncCallCount = new AtomicInteger();
AtomicInteger asyncCallCount = new AtomicInteger();
ContainerRegistryCredentialsPolicy policy = new ContainerRegistryCredentialsPolicy(this.service, "foo") {
@Override
public Mono<Void> setAuthorizationHeader(HttpPipelineCallContext context,
TokenRequestContext tokenRequestContext) {
asyncCallCount.incrementAndGet();
return super.setAuthorizationHeader(context, tokenRequestContext);
}

@Override
public void setAuthorizationHeaderSync(HttpPipelineCallContext context,
TokenRequestContext tokenRequestContext) {
syncCallCount.incrementAndGet();
super.setAuthorizationHeaderSync(context, tokenRequestContext);
}
};

HttpPipeline pipeline = new HttpPipelineBuilder()
.policies(policy)
.httpClient(request -> Mono.just(successResponse))
.build();

SyncAsyncExtension.execute(() -> pipeline.sendSync(REQUEST, Context.NONE), () -> pipeline.send(REQUEST));

// Make sure no call being done to the authorize request.
verify(spyPolicy, times(0)).setAuthorizationHeader(any(HttpPipelineCallContext.class), any(ContainerRegistryTokenRequestContext.class));
verify(spyPolicy, times(0)).setAuthorizationHeaderSync(any(HttpPipelineCallContext.class), any(ContainerRegistryTokenRequestContext.class));
assertEquals(0, asyncCallCount.get());
assertEquals(0, syncCallCount.get());

when(nextPolicy.process()).thenReturn(Mono.just(unauthorizedHttpResponseWithoutHeader));
when(nextSyncPolicy.processSync()).thenReturn(unauthorizedHttpResponseWithoutHeader);
HttpPipeline pipeline2 = new HttpPipelineBuilder()
.policies(policy)
.httpClient(request -> Mono.just(unauthorizedHttpResponseWithoutHeader))
.build();

SyncAsyncExtension.execute(
() -> policy.processSync(this.callContext, this.nextSyncPolicy),
() -> policy.process(this.callContext, this.nextPolicy)
);
SyncAsyncExtension.execute(() -> pipeline2.sendSync(REQUEST, Context.NONE), () -> pipeline2.send(REQUEST));

// Make sure no call being done to the authorize request.
verify(spyPolicy, times(0)).setAuthorizationHeader(any(HttpPipelineCallContext.class), any(ContainerRegistryTokenRequestContext.class));
verify(spyPolicy, times(0)).setAuthorizationHeaderSync(any(HttpPipelineCallContext.class), any(ContainerRegistryTokenRequestContext.class));
assertEquals(0, asyncCallCount.get());
assertEquals(0, syncCallCount.get());
}

@Test
public void requestAddBearerTokenToRequest() {
ContainerRegistryCredentialsPolicy policy = new ContainerRegistryCredentialsPolicy(this.service, "foo");
ContainerRegistryCredentialsPolicy spyPolicy = Mockito.spy(policy);
AtomicReference<TokenRequestContext> contextReference = new AtomicReference<>();
AtomicInteger callCount = new AtomicInteger();
ContainerRegistryCredentialsPolicy policy = new ContainerRegistryCredentialsPolicy(this.service, "foo") {
@Override
public Mono<Void> setAuthorizationHeader(HttpPipelineCallContext context,
TokenRequestContext tokenRequestContext) {
callCount.getAndIncrement();
contextReference.set(tokenRequestContext);
return super.setAuthorizationHeader(context, tokenRequestContext);
}
};

// Validate that the onChallenge ran successfully.
StepVerifier.create(spyPolicy.authorizeRequestOnChallenge(this.callContext, this.unauthorizedHttpResponse))
StepVerifier.create(policy.authorizeRequestOnChallenge(this.callContext, this.unauthorizedHttpResponse))
.assertNext(Assertions::assertTrue)
.verifyComplete();

String tokenValue = this.callContext.getHttpRequest().getHeaders().getValue(HttpHeaderName.AUTHORIZATION);
assertFalse(tokenValue.isEmpty());
assertTrue(tokenValue.startsWith(BEARER));
assertTrue(tokenValue.endsWith(tokenValue));

// Validate that the token creation was called with the correct arguments.
ArgumentCaptor<ContainerRegistryTokenRequestContext> argument = ArgumentCaptor.forClass(ContainerRegistryTokenRequestContext.class);
verify(spyPolicy).setAuthorizationHeader(any(HttpPipelineCallContext.class), argument.capture());

ContainerRegistryTokenRequestContext requestContext = argument.getValue();
assertEquals(1, callCount.get());
ContainerRegistryTokenRequestContext requestContext = assertInstanceOf(
ContainerRegistryTokenRequestContext.class, contextReference.get());
assertEquals(SERVICENAME, requestContext.getServiceName());
assertEquals(SCOPENAME, requestContext.getScopes().get(0));
}

@Test
public void requestAddBearerTokenToRequestSync() {
ContainerRegistryCredentialsPolicy policy = new ContainerRegistryCredentialsPolicy(this.service, "foo");
ContainerRegistryCredentialsPolicy spyPolicy = Mockito.spy(policy);

boolean onChallenge = spyPolicy.authorizeRequestOnChallengeSync(this.callContext, this.unauthorizedHttpResponse);
AtomicReference<TokenRequestContext> contextReference = new AtomicReference<>();
AtomicInteger callCount = new AtomicInteger();
ContainerRegistryCredentialsPolicy policy = new ContainerRegistryCredentialsPolicy(this.service, "foo") {
@Override
public void setAuthorizationHeaderSync(HttpPipelineCallContext context,
TokenRequestContext tokenRequestContext) {
callCount.getAndIncrement();
contextReference.set(tokenRequestContext);
super.setAuthorizationHeaderSync(context, tokenRequestContext);
}
};

boolean onChallenge = policy.authorizeRequestOnChallengeSync(this.callContext, this.unauthorizedHttpResponse);

// Validate that the onChallenge ran successfully.
assertTrue(onChallenge);

String tokenValue = this.callContext.getHttpRequest().getHeaders().getValue(HttpHeaderName.AUTHORIZATION);
assertFalse(tokenValue.isEmpty());
assertTrue(tokenValue.startsWith(BEARER));
assertTrue(tokenValue.endsWith(tokenValue));

// Validate that the token creation was called with the correct arguments.
ArgumentCaptor<ContainerRegistryTokenRequestContext> argument = ArgumentCaptor.forClass(ContainerRegistryTokenRequestContext.class);
verify(spyPolicy).setAuthorizationHeaderSync(any(HttpPipelineCallContext.class), argument.capture());

ContainerRegistryTokenRequestContext requestContext = argument.getValue();
assertEquals(1, callCount.get());
ContainerRegistryTokenRequestContext requestContext = assertInstanceOf(
ContainerRegistryTokenRequestContext.class, contextReference.get());
assertEquals(SERVICENAME, requestContext.getServiceName());
assertEquals(SCOPENAME, requestContext.getScopes().get(0));
}
Expand Down
Loading

0 comments on commit c5b98ab

Please sign in to comment.