Тестирование API REST с помощью Screenplay: получите значение из результата задачи и поделитесь им с другими задачами - PullRequest
0 голосов
/ 18 марта 2020

Я пытаюсь реализовать шаг входа в систему с помощью REST API с использованием Serenity Screenplay Pattern . Чтобы это работало, мне нужно иметь возможность получить токен JWT, который был возвращен из одной Задачи, и использовать его для аутентификации других задач. Я знаю, как это сделать в теории. Пожалуйста, рассмотрите следующий Groovy код.

class ActorInTheSpotlight {
    String actor
    String token

    @Step("{0} is an actor using the system under test")
    ActorInTheSpotlight whoIsNamed(String actor) {
        this.actor = actor
        theActorCalled(actor)

        return this
    }

    @Step("#actor who can authenticate with credentials")
    ActorInTheSpotlight whoCanAuthenticateWith(String email, String password) {
        theActor().whoCan(Authenticate.withCredentials(email, password))

        return this
    }

    @Step("#actor who can call the admin API")
    ActorInTheSpotlight whoCanCallTheAdminApi() {
        theActor().whoCan(CallAnApi.at("http://localhost:3000"))
        return this
    }

    @Step("#actor was able to login to API with credentials")
    ActorInTheSpotlight wasAbleToLoginToApi() {
        return wasAbleTo(LoginWithApi.usingCredentials())
    }

    ActorInTheSpotlight wasAbleTo(Performable... todos) {
        theActor().wasAbleTo(todos)
        return this
    }
}
class LoginWithApi implements Task {

    @Shared
    ActorInTheSpotlight theActor

    static LoginWithApi usingCredentials() {
        return instrumented(LoginWithApi.class);
    }

    @Step("{0} logs into api using credentials")
    <T extends Actor> void performAs(T actor) {
        def auth = Authenticate.asPrincipal(actor)
        actor.attemptsTo(
                // NOTE: PostToApi is an alias for Post, renaming `with` to `withRequest`
                // so that Groovy does not attempt to match it to the default `with(Closure closure)`
                PostToApi.at("/login").withRequest({ RequestSpecification req ->
                    req.header("Content-Type", "application/json")
                        .body([email: auth.email, password: auth.password])
                })
        )

    }
}
class AdminApiStepDefinitions {

    @Shared
    ActorInTheSpotlight theActor

    @Before
    void set_the_stage(){
        OnStage.setTheStage(new OnlineCast())
    }

    @Given(/^that "([^"]*)" is an Admin who may call the rest api$/)
    void is_an_admin_who_may_call_the_rest_api(String actor) {
        theActor.whoIsNamed(actor)
                .whoCanCallTheAdminApi()
    }

    @Given(/^s?he was able to login to the api with the credentials$/)
    void was_able_to_login_to_the_api_with_the_credentials(Map<String, String> credentials) {
        def email = credentials.get('email')
        def password = credentials.get('password')
        theActor
                .whoCanAuthenticateWith(email, password)
                .wasAbleToLoginToApi()
    }
}

Так что, теоретически, я должен быть в состоянии разделить шаги ActorInTheSpotlight между задачами, используя его для хранения / извлечения моего Токен JWT Я также вижу, что могу получить значение токена следующим образом:

String token = SerenityRest.lastResponse() 
        .jsonPath()
        .getObject("token", String.class);

Проблема в том, что я не совсем уверен, куда поместить этот код в контексте определений шагов. Должен ли я реализовать извлечение этого токена как свой собственный шаг или есть способ скрыть детали реализации в самой задаче LoginToApi?

Спасибо за ваше время!

Обновление

Вот класс способностей Authenticate, который, вероятно, был бы хорошим местом для реализации этой функциональности, но все еще применяются те же проблемы синхронизации, что и выше. IE, как мне обновить способность "в полете", чтобы она была доступна в нужное время для использования в других задачах.

class Authenticate implements Ability {

    String email

    String password

    // instantiates the Ability and enables fluent DSL
    static Authenticate withCredentials(String email, String password) {
        return new Authenticate(email, password)
    }

    // NOTE: custom exception class not shown
    static Authenticate asPrincipal(Actor actor) throws CannotAuthenticateException {
        // complain if someone's asking the impossible
        if(!actor.abilityTo(Authenticate.class)){
            throw new CannotAuthenticateException(actor.getName())
        }

        return actor.abilityTo(Authenticate.class)
    }

    Authenticate(String email, String password) {
        this.email = email
        this.password = password
    }
}

Обновление 2

Мне удалось реализовать это как отдельный шаг, но мне очень не нравятся мои детали реализации, просачивающиеся в определения шагов, подобные этому. За исключением любого ответа, который позволяет мне реализовать это без шага was_able_to_get_a_valid_jwt_token, показанного ниже.

примечание: показаны только дополнения к исходному коду

class ActorInTheSpotlight {
    @Step("#actor has a valid JWT token")
    ActorInTheSpotlight whoHasTheToken(String token) {
        this.token = token
        theActor().whoCan(AuthenticateApi.withToken(token))
        return this
    }
}
class AuthenticateApi implements Ability {

    String token

    static AuthenticateApi withToken(String token) {
        return new AuthenticateApi(token)
    }

    static AuthenticateApi asPrincipal(Actor actor) throws CannotAuthenticateException {
        // complain if someone's asking the impossible
        if(!actor.abilityTo(AuthenticateApi.class)){
            throw new CannotAuthenticateException(actor.getName())
        }

        return actor.abilityTo(AuthenticateApi.class)
    }

    static <T extends Actor> void attempt(final T actor, final RequestSpecification req) {
        AuthenticateApi auth = null
        try {
            auth = AuthenticateApi.asPrincipal(actor)
        }
        catch(CannotAuthenticateException e) {
            // swallow error
        }

        if(auth) {
            req.header("Authorization", "Bearer ${auth.token}")
        }
    }

    AuthenticateApi(String token) {
        this.token = token
    }
}
class AdminApiStepDefinitions {
    // This is what I want to get rid of!
    @Given(/^s?he was able to get a valid JWT token$/)
    void was_able_to_get_a_valid_jwt_token() {
        theActor.whoHasTheToken(SerenityRest.lastResponse().jsonPath()
                .getObject("token", String.class))
    }
}

А вот пример Задачи, использующей токен JWT для аутентификации запросов:

class ApiGet implements Task {

    static ApiGet from(String resource) {
        return instrumented(ApiGet.class, resource)
    }

    String resource

    ApiGet(String resource) {
        this.resource = resource
    }

    @Step("{0} attempts to GET #resource")
    <T extends Actor> void performAs(T actor) {
        actor.attemptsTo(
                // NOTE: GetFromApi is an alias for Get, renaming `with` to `withRequest`
                // so that Groovy does not attempt to match it to the default `with(Closure closure)`
                GetFromApi.at(resource).withRequest({ RequestSpecification req ->
                    AuthenticateApi.attempt(actor, req)
                    req.header("Content-Type", "application/json")
                })
        )
    }
}

1 Ответ

0 голосов
/ 21 марта 2020

Ну, это не похоже на многопоточность, но на самом деле ничего этого нет, так что ... Мех. Вот что я придумал.

class AdminApiStepDefinitions {
    @Given(/^s?he was able to login to the api with the credentials$/)
    void was_able_to_login_to_the_api_with_the_credentials(Map<String, String> credentials) {
        def email = credentials.get('email')
        def password = credentials.get('password')
        theActor
                .whoCanAuthenticateWith(email, password)
                .wasAbleToLoginToApi()

        theActor.whoHasTheToken(SerenityRest.lastResponse().jsonPath().getString("token"))
    }
}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...