Skip to content

Commit

Permalink
stream #22 - oauth (qa-guru#63)
Browse files Browse the repository at this point in the history
# Conflicts:
#	niffler-e-2-e-tests/src/test/java/guru/qa/niffler/api/AuthApi.java
#	niffler-e-2-e-tests/src/test/java/guru/qa/niffler/jupiter/extension/UserExtension.java
#	niffler-e-2-e-tests/src/test/java/guru/qa/niffler/test/web/ProfileTest.java
#	niffler-e-2-e-tests/src/test/java/utils/OAuthUtils.java
  • Loading branch information
dtuchs authored and romketa committed Dec 16, 2024
1 parent 907a51f commit 5929fbb
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 29 deletions.
1 change: 1 addition & 0 deletions niffler-e-2-e-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ dependencies {
// REST
testImplementation "com.squareup.retrofit2:retrofit:${retrofitVersion}"
testImplementation "com.squareup.retrofit2:converter-jackson:${retrofitVersion}"
testImplementation "com.squareup.retrofit2:converter-scalars:${retrofitVersion}"
testImplementation "com.squareup.okhttp3:logging-interceptor:${okhttp3Version}"
testImplementation "com.squareup.okhttp3:okhttp-urlconnection:${okhttp3Version}"
testImplementation "org.springframework.data:spring-data-commons:${springDataCommonsVersion}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.io.IOException;
import okhttp3.ResponseBody;
import retrofit2.Response;
import retrofit2.http.Field;

public class AuthApiClient extends RestClient {

Expand Down Expand Up @@ -38,6 +39,18 @@ public void requestRegisterForm() {
assertEquals(200, response.code());
}

@Step("Register")
public void register(String username, String password, String passwordSubmit, String csrf) {
final Response<Void> response;
try {
response = authApi.register(username, password, passwordSubmit, csrf)
.execute();
} catch (IOException e) {
throw new AssertionError(e);
}
assertEquals(200, response.code());
}

@Step("Authorize")
public void authorize(String codeChallenge) {
final Response<ResponseBody> response;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package guru.qa.niffler.api.core;

import guru.qa.niffler.jupiter.extension.ApiLoginExtension;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.Objects;

public class CodeInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
final Response response = chain.proceed(chain.request());
if (response.isRedirect()) {
String location = Objects.requireNonNull(
response.header("Location")
);
if (location.contains("code=")) {
ApiLoginExtension.setCode(
StringUtils.substringAfter(
location, "code="
)
);
}
}
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public RestClient(String baseUrl, boolean followRedirect) {
this(baseUrl, followRedirect, JacksonConverterFactory.create(), HEADERS, new Interceptor[0]);
}

public RestClient(String baseUrl, boolean followRedirect, @Nullable Interceptor... interceptors) {
this(baseUrl, followRedirect, JacksonConverterFactory.create(), HEADERS, interceptors);
}

public RestClient(String baseUrl, HttpLoggingInterceptor.Level loggingLevel) {
this(baseUrl, false, JacksonConverterFactory.create(), loggingLevel, new Interceptor[0]);
}
Expand Down Expand Up @@ -75,4 +79,8 @@ public RestClient(String baseUrl, boolean followRedirect, Converter.Factory conv
.addConverterFactory(converterFactory)
.build();
}

public <T> T create(final Class<T> service) {
return this.retrofit.create(service);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package guru.qa.niffler.jupiter.annotation;

import guru.qa.niffler.jupiter.extension.ApiLoginExtension;
import guru.qa.niffler.jupiter.extension.UserExtension;
import org.junit.jupiter.api.extension.ExtendWith;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ExtendWith({
UserExtension.class,
ApiLoginExtension.class
})
public @interface ApiLogin {
String username() default "";
String password() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package guru.qa.niffler.jupiter.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Token {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package guru.qa.niffler.jupiter.extension;

import com.codeborne.selenide.Selenide;
import com.codeborne.selenide.WebDriverRunner;
import guru.qa.niffler.api.core.ThreadSafeCookieStore;
import guru.qa.niffler.config.Config;
import guru.qa.niffler.jupiter.annotation.ApiLogin;
import guru.qa.niffler.jupiter.annotation.Token;
import guru.qa.niffler.model.TestData;
import guru.qa.niffler.model.UserJson;
import guru.qa.niffler.page.MainPage;
import guru.qa.niffler.service.impl.AuthApiClient;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.platform.commons.support.AnnotationSupport;
import org.openqa.selenium.Cookie;


public class ApiLoginExtension implements BeforeEachCallback, ParameterResolver {

private static final Config CFG = Config.getInstance();
public static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(ApiLoginExtension.class);

private final AuthApiClient authApiClient = new AuthApiClient();
private final boolean setupBrowser;

private ApiLoginExtension(boolean setupBrowser) {
this.setupBrowser = setupBrowser;
}

public ApiLoginExtension() {
this.setupBrowser = true;
}

public static ApiLoginExtension restApiLoginExtension() {
return new ApiLoginExtension(false);
}

@Override
public void beforeEach(ExtensionContext context) throws Exception {
AnnotationSupport.findAnnotation(context.getRequiredTestMethod(), ApiLogin.class)
.ifPresent(apiLogin -> {

final UserJson userToLogin;
final UserJson userFromUserExtension = UserExtension.getUserJson();
if ("".equals(apiLogin.username()) || "".equals(apiLogin.password())) {
if (userFromUserExtension == null) {
throw new IllegalStateException("@User must be present in case that @ApiLogin is empty!");
}
userToLogin = userFromUserExtension;
} else {
UserJson fakeUser = new UserJson(
apiLogin.username(),
new TestData(
apiLogin.password()
)
);
if (userFromUserExtension != null) {
throw new IllegalStateException("@User must not be present in case that @ApiLogin contains username or password!");
}
UserExtension.setUser(fakeUser);
userToLogin = fakeUser;
}

final String token = authApiClient.login(
userToLogin.username(),
userToLogin.testData().password()
);
setToken(token);
if (setupBrowser) {
Selenide.open(CFG.frontUrl());
Selenide.localStorage().setItem("id_token", getToken());
WebDriverRunner.getWebDriver().manage().addCookie(
new Cookie(
"JSESSIONID",
ThreadSafeCookieStore.INSTANCE.cookieValue("JSESSIONID")
)
);
Selenide.open(MainPage.URL, MainPage.class).checkThatPageLoaded();
}
});
}

@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType().isAssignableFrom(String.class)
&& AnnotationSupport.isAnnotated(parameterContext.getParameter(), Token.class);
}

@Override
public String resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getToken();
}

public static void setToken(String token) {
TestMethodContextExtension.context().getStore(NAMESPACE).put("token", token);
}

public static String getToken() {
return TestMethodContextExtension.context().getStore(NAMESPACE).get("token", String.class);
}

public static void setCode(String code) {
TestMethodContextExtension.context().getStore(NAMESPACE).put("code", code);
}

public static String getCode() {
return TestMethodContextExtension.context().getStore(NAMESPACE).get("code", String.class);
}

public static Cookie getJsessionIdCookie() {
return new Cookie(
"JSESSIONID",
ThreadSafeCookieStore.INSTANCE.cookieValue("JSESSIONID")
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ public void beforeEach(ExtensionContext context) throws Exception {
usersClient.addIncomeInvitations(testUser, userAnno.incomeInvitations());
usersClient.addOutcomeInvitations(testUser, userAnno.outcomeInvitations());
usersClient.addFriends(testUser, userAnno.friends());
context.getStore(NAMESPACE).put(
context.getUniqueId(),
testUser
);
setUser(testUser);
}
});
}
Expand All @@ -46,8 +43,20 @@ public boolean supportsParameter(ParameterContext parameterContext,
}

@Override
public UserJson resolveParameter(ParameterContext parameterContext,
ExtensionContext extensionContext) throws ParameterResolutionException {
return extensionContext.getStore(NAMESPACE).get(extensionContext.getUniqueId(), UserJson.class);
public UserJson resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getUserJson();
}

public static void setUser(UserJson testUser) {
final ExtensionContext context = TestMethodContextExtension.context();
context.getStore(NAMESPACE).put(
context.getUniqueId(),
testUser
);
}

public static UserJson getUserJson() {
final ExtensionContext context = TestMethodContextExtension.context();
return context.getStore(NAMESPACE).get(context.getUniqueId(), UserJson.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import guru.qa.niffler.data.entity.userdata.UserEntity;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import jaxb.userdata.FriendState;

public record UserJson(
Expand Down Expand Up @@ -47,6 +49,14 @@ public static UserJson fromEntity(UserEntity entity, FriendState friendState) {
);
}

public UserJson(@Nonnull String username) {
this(username, null);
}

public UserJson(@Nonnull String username, @Nullable TestData testData) {
this(null, username, null, null, null, null, null, null, null, testData);
}

public UserJson addTestData(TestData testData) {
return new UserJson(
id, username, firstname, surname, fullname, currency, photo, photoSmall, friendState, testData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@ParametersAreNonnullByDefault
public class MainPage extends BasePage<MainPage> {

public static final String URL = CFG.frontUrl() + "main";
private final SelenideElement tableRows = $("#spendings tbody");
private SelenideElement statisticsBlock = $x("//div[h2[contains(text(), 'Statistics')]]");
private final SelenideElement historyOfSpending = $x(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
@ParametersAreNonnullByDefault
public class ProfilePage extends BasePage<ProfilePage> {

public static String url = Config.getInstance().frontUrl() + "profile";
public static final String URL = CFG.frontUrl() + "profile";
private final SelenideElement avatar = $("#image__input").parent().$("img");
private final SelenideElement userName = $("#username");
private final SelenideElement nameInput = $("#name");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package guru.qa.niffler.service.impl;

import com.fasterxml.jackson.databind.JsonNode;
import guru.qa.niffler.api.AuthApi;
import guru.qa.niffler.api.core.CodeInterceptor;
import guru.qa.niffler.api.core.RestClient;
import guru.qa.niffler.api.core.ThreadSafeCookieStore;
import guru.qa.niffler.config.Config;
import guru.qa.niffler.jupiter.extension.ApiLoginExtension;
import utils.OAuthUtils;
import lombok.SneakyThrows;
import retrofit2.Response;


public class AuthApiClient extends RestClient {

private static final Config CFG = Config.getInstance();
private final AuthApi authApi;

public AuthApiClient() {
super(CFG.authUrl(), true, new CodeInterceptor());
this.authApi = create(AuthApi.class);
}

@SneakyThrows
public String login(String username, String password) {
final String codeVerifier = OAuthUtils.generateCodeVerifier();
final String codeChallenge = OAuthUtils.generateCodeChallenge(codeVerifier);
final String redirectUri = CFG.frontUrl() + "authorized";
final String clientId = "client";

authApi.authorize(
"code",
clientId,
"openid",
redirectUri,
codeChallenge,
"S256"
).execute();

authApi.login(
username,
password,
ThreadSafeCookieStore.INSTANCE.cookieValue("XSRF-TOKEN")
).execute();

Response<JsonNode> tokenResponse = authApi.token(
ApiLoginExtension.getCode(),
redirectUri,
clientId,
codeVerifier,
"authorization_code"
).execute();

return tokenResponse.body().get("id_token").asText();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import guru.qa.niffler.model.UserJson;
import guru.qa.niffler.service.UsersClient;
import io.qameta.allure.Step;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package guru.qa.niffler.test.web;
package guru.qa.niffler.test.fake;

import guru.qa.niffler.jupiter.extension.UsersClientExtension;
import guru.qa.niffler.model.CategoryJson;
Expand Down
Loading

0 comments on commit 5929fbb

Please sign in to comment.