当前位置: 首页 > 知识库问答 >
问题:

给定一个Ratpack RequestFixture测试,我如何让fixture调用request#beforesend?

斜向文
2023-03-14

例如,该测试用例由于断言www-authenticate标头存在而失败,即使在实际应用程序中调用时,根据该标头进行修改的代码正确地插入了标头。测试中的链是testchain,跳到最后查找失败的断言:

package whatever

import com.google.inject.Module
import groovy.transform.Canonical
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.pac4j.core.profile.jwt.JwtClaims
import org.pac4j.http.client.direct.HeaderClient
import org.pac4j.jwt.config.encryption.EncryptionConfiguration
import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration
import org.pac4j.jwt.config.signature.SecretSignatureConfiguration
import org.pac4j.jwt.config.signature.SignatureConfiguration
import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator
import org.pac4j.jwt.profile.JwtGenerator
import org.pac4j.jwt.profile.JwtProfile
import ratpack.groovy.handling.GroovyChainAction
import ratpack.groovy.test.handling.GroovyRequestFixture
import ratpack.guice.Guice
import ratpack.http.Response
import ratpack.http.Status
import ratpack.jackson.Jackson
import ratpack.pac4j.RatpackPac4j
import ratpack.registry.Registry
import ratpack.session.SessionModule
import ratpack.test.handling.HandlerExceptionNotThrownException
import ratpack.test.handling.HandlingResult
import spock.lang.Specification

@CompileStatic
class AuthenticatorTest extends Specification {
    static byte[] salt = new byte[32] // dummy salt

    static SignatureConfiguration signatureConfiguration = new SecretSignatureConfiguration(salt)
    static EncryptionConfiguration encryptionConfiguration = new SecretEncryptionConfiguration(salt)
    static JwtAuthenticator authenticator = new JwtAuthenticator(signatureConfiguration, encryptionConfiguration)
    static JwtGenerator generator = new JwtGenerator(signatureConfiguration, encryptionConfiguration)
    static HeaderClient headerClient = new HeaderClient("Authorization", "bearer ", authenticator)

    /** A stripped down user class */
    @Canonical
    static class User {
        final String id
    }

    /** A stripped down user registry class */
    @Canonical
    static class UserRegistry {
        private final Map<String, String> users = [
            'joebloggs': 'sekret'
        ]

        User authenticate(String id, String password) {
            if (password != null && users[id] == password)
               return new User(id)
            return null
        }
    }

    /** Generates a JWT token for a given user
     *
     * @param userId - the name of the user
     * @return A JWT token encoded as a string
     */
    static String generateToken(String userId) {
        JwtProfile profile = new JwtProfile()
        profile.id = userId
        profile.addAttribute(JwtClaims.ISSUED_AT, new Date())
        String token = generator.generate(profile)
        token
    }

    static void trapExceptions(HandlingResult result) {
        try {
            Throwable t = result.exception(Throwable)
            throw t
        }
        catch (HandlerExceptionNotThrownException ignored) {
        }
    }

    /** Composes a new registry binding the module class passed
     * as per SO question https://stackoverflow.com/questions/50814817/how-do-i-mock-a-session-in-ratpack-with-requestfixture
     */
    static Registry addModule(Registry registry, Class<? extends Module> module) {
        Guice.registry { it.module(module) }.apply(registry)
    }

    GroovyChainAction testChain = new GroovyChainAction() {
        @Override
        @CompileDynamic
        void execute() throws Exception {

            register addModule(registry, SessionModule)

            all RatpackPac4j.authenticator(headerClient)

            all {
                /*
                 * This is a workaround for an issue in RatpackPac4j v2.0.0, which doesn't
                 * add the WWW-Authenticate header by itself.
                 *
                 * See https://github.com/pac4j/ratpack-pac4j/issues/3
                 *
                 * This handler needs to be ahead of any potential causes of 401 statuses
                 */
                response.beforeSend { Response response ->
                    if (response.status.code == 401) {
                        response.headers.set('WWW-Authenticate', 'bearer realm="authenticated api"')
                    }
                }
                next()
            }

            post('login') { UserRegistry users ->
                parse(Jackson.fromJson(Map)).then { Map data ->
                    // Validate the credentials
                    String id = data.user
                    String password = data.password
                    User user = users.authenticate(id, password)
                    if (user == null) {
                        clientError(401) // Bad authentication credentials
                    } else {
                        response.contentType('text/plain')

                        // Authenticates ok. Issue a JWT token to the client which embeds (signed, encrypted)
                        // certain standardised metadata of our choice that the JWT validation will use.
                        String token = generateToken(user.id)
                        render token
                    }
                }
            }

            get('unprotected') {
                render "hello"
            }

            // All subsequent paths require authentication
            all RatpackPac4j.requireAuth(HeaderClient)

            get('protected') {
                render "hello"
            }

            notFound()
        }
    }

    @CompileDynamic
    def "should be denied protected path, unauthorised..."() {
        given:
        def result = GroovyRequestFixture.handle(testChain) {
            uri 'protected'
            method 'GET'
        }

        expect:
        result.status == Status.of(401) // Unauthorized


        // THIS FAILS BECAUSE Response#beforeSend WASN'T INVOKED BY GroovyRequestFixture
        result.headers['WWW-Authenticate'] == 'bearer realm="authenticated api"'

        // If the server threw, rethrow that
        trapExceptions(result)
    }
}

共有1个答案

漆雕欣德
2023-03-14

到目前为止最好的答案...或者更严格地说,避开RequestFixture限制的一个解决方案是:不要使用RequestFixture。使用GroovyEmbeddedApp

(在Ratpack slack频道上归功于丹贤)

RequestFixture只是用来检查处理程序的行为,它不会做很多事情--它不会序列化响应。EmbeddedApp可能是大多数测试的首选。您会更关心整体交互,而不是单个处理程序如何完成某件事情,除非它是高度重用的组件或被其他应用程序使用的中间件

package whatever

import com.google.inject.Module
import groovy.transform.Canonical
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.pac4j.core.profile.jwt.JwtClaims
import org.pac4j.http.client.direct.HeaderClient
import org.pac4j.jwt.config.encryption.EncryptionConfiguration
import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration
import org.pac4j.jwt.config.signature.SecretSignatureConfiguration
import org.pac4j.jwt.config.signature.SignatureConfiguration
import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator
import org.pac4j.jwt.profile.JwtGenerator
import org.pac4j.jwt.profile.JwtProfile
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.guice.Guice
import ratpack.http.Response
import ratpack.http.Status
import ratpack.http.client.ReceivedResponse
import ratpack.jackson.Jackson
import ratpack.pac4j.RatpackPac4j
import ratpack.registry.Registry
import ratpack.session.SessionModule
import ratpack.test.handling.HandlerExceptionNotThrownException
import ratpack.test.handling.HandlingResult
import ratpack.test.http.TestHttpClient
import spock.lang.Specification

@CompileStatic
class TempTest extends Specification {
    static byte[] salt = new byte[32] // dummy salt

    static SignatureConfiguration signatureConfiguration = new SecretSignatureConfiguration(salt)
    static EncryptionConfiguration encryptionConfiguration = new SecretEncryptionConfiguration(salt)
    static JwtAuthenticator authenticator = new JwtAuthenticator(signatureConfiguration, encryptionConfiguration)
    static JwtGenerator generator = new JwtGenerator(signatureConfiguration, encryptionConfiguration)
    static HeaderClient headerClient = new HeaderClient("Authorization", "bearer ", authenticator)

    /** A stripped down user class */
    @Canonical
    static class User {
        final String id
    }

    /** A stripped down user registry class */
    @Canonical
    static class UserRegistry {
        private final Map<String, String> users = [
            'joebloggs': 'sekret'
        ]

        User authenticate(String id, String password) {
            if (password != null && users[id] == password)
                return new User(id)
            return null
        }
    }

    /** Generates a JWT token for a given user
     *
     * @param userId - the name of the user
     * @return A JWT token encoded as a string
     */
    static String generateToken(String userId) {
        JwtProfile profile = new JwtProfile()
        profile.id = userId
        profile.addAttribute(JwtClaims.ISSUED_AT, new Date())
        String token = generator.generate(profile)
        token
    }

    static void trapExceptions(HandlingResult result) {
        try {
            Throwable t = result.exception(Throwable)
            throw t
        }
        catch (HandlerExceptionNotThrownException ignored) {
        }
    }

    /** Composes a new registry binding the module class passed
     * as per SO question https://stackoverflow.com/questions/50814817/how-do-i-mock-a-session-in-ratpack-with-requestfixture
     */
    static Registry addModule(Registry registry, Class<? extends Module> module) {
        Guice.registry { it.module(module) }.apply(registry)
    }

    /*** USE GroovyEmbeddedApp HERE INSTEAD OF GroovyResponseFixture ***/
    GroovyEmbeddedApp testApp = GroovyEmbeddedApp.ratpack {
        bindings {
            module SessionModule
        }

        handlers {
            all RatpackPac4j.authenticator(headerClient)

            all {
                /*
                 * This is a workaround for an issue in RatpackPac4j v2.0.0, which doesn't
                 * add the WWW-Authenticate header by itself.
                 *
                 * See https://github.com/pac4j/ratpack-pac4j/issues/3
                 *
                 * This handler needs to be ahead of any potential causes of 401 statuses
                 */
                response.beforeSend { Response response ->
                    if (response.status.code == 401) {
                        response.headers.set('WWW-Authenticate', 'bearer realm="authenticated api"')
                    }
                }
                next()
            }

            post('login') { UserRegistry users ->
                parse(Jackson.fromJson(Map)).then { Map data ->
                    // Validate the credentials
                    String id = data.user
                    String password = data.password
                    User user = users.authenticate(id, password)
                    if (user == null) {
                        clientError(401) // Bad authentication credentials
                    } else {
                        response.contentType('text/plain')

                        // Authenticates ok. Issue a JWT token to the client which embeds (signed, encrypted)
                        // certain standardised metadata of our choice that the JWT validation will use.
                        String token = generateToken(user.id)
                        render token
                    }
                }
            }

            get('unprotected') {
                render "hello"
            }

            // All subsequent paths require authentication
            all RatpackPac4j.requireAuth(HeaderClient)

            get('protected') {
                render "hello"
            }

            notFound()
        }
    }


    /*** THIS NOW ALTERED TO USE testApp ***/
    @CompileDynamic
    def "should be denied protected path, unauthorised..."() {
        given:
        TestHttpClient client = testApp.httpClient
        ReceivedResponse response = client.get('protected')

        expect:
        response.status == Status.of(401) // Unauthorized
        response.headers['WWW-Authenticate'] == 'bearer realm="authenticated api"'
    }
}
 类似资料:
  • 我正在努力增加我在Android上的代码覆盖率。但我找不到测试这个演示者的正确方法。onSelectContact会进行一个服务调用,随后my ServiceFactory.GetContactService会进行另一个调用。我怎么能嘲笑这些电话呢?

  • 如何从Clojure REPL运行一个测试(而不是整个命名空间)? 我尝试过直接调用函数,例如,但我需要先运行fixture。所以我想找到一种从。 这很接近,但不匹配我想做的:https://stackoverflow.com/a/24337705/109618 我看不到clojure有提到怎么做。测试API。

  • 像往常一样,我有一个选项卡式活动,通过viewpager显示片段。此片段有一个列表。 用户的一个操作显示一个dialogfragment,以便用户在此列表中插入一个新项。 我显示带有EditText的dialogfragment,以便用户创建一个新项目。 问题是:如何将此项目插入到viewpager的片段列表中? 从任何片段中,我都可以调用getActive()来访问活动,但是如何访问对话框片段后

  • 我必须为一个调用API然后处理响应的类编写测试。类有两个公共函数和一个私有函数。第一个公共方法获取ID列表。在循环中为每个ID调用第二个公共方法,以获取与ID关联的详细信息。私有方法是在第二个公共方法内部调用的,因为获取基于id的详细信息的调用是异步进行的。 我是JUnits的新手,虽然我知道我不应该测试API调用,只是测试我的函数,但我仍然不明白单元测试应该断言什么。

  • 我这里有一个问题,请给出一些想法。 我有两个豆子。FaceComparisonServerImpl依赖于FaceServer。当我想测试的时候。我想更改我的'FaceServer'bean中的字符串。 贝娄是我的测试代码。 我的问题是:我如何修改@bean(faceServer)中'version'和'server_url'的值? 谢谢你!

  • 问题内容: 我正在使用JavaScript测试运行程序“摩卡”。 我的测试失败了,因此我将使用进行调试。 但是运行测试时,没有输出(仅来自Mocha的测试结果)。看来Mocha已捕获并抑制了我的输出! 如何让Mocha显示输出?(对于失败的测试)? 编辑: 抱歉!- 在测试期间可以正常工作!我肯定一直期望它抑制输出,而且我没有正确检查自己的代码。感谢您的回应。所以…话虽如此…也许抑制通过测试的输出