Java 给定Ratpack RequestFixture测试,如何在发送之前让fixture调用请求?

Java 给定Ratpack RequestFixture测试,如何在发送之前让fixture调用请求?,java,groovy,ratpack,www-authenticate,Java,Groovy,Ratpack,Www Authenticate,此问题是在RatpackRequestFixtureSpock测试的上下文中,对于使用RatpackPac4j#requireAuth进行身份验证的Ratpack链,并对缺少的WWW Authenticate头(如对的回答中所述)采用解决方法 我的问题是,当从GroovyRequestFixture#handle(一个RequestFixture#handle的包装器)获得响应时,我发现#beforeSend似乎没有被调用。解决方法取决于此,所以我无法测试它。是否有方法在返回的处理结果所表示的响

此问题是在Ratpack
RequestFixture
Spock测试的上下文中,对于使用
RatpackPac4j#requireAuth
进行身份验证的Ratpack链,并对缺少的
WWW Authenticate
头(如对的回答中所述)采用解决方法

我的问题是,当从
GroovyRequestFixture#handle
(一个
RequestFixture#handle
的包装器)获得响应时,我发现
#beforeSend
似乎没有被调用。解决方法取决于此,所以我无法测试它。是否有方法在返回的
处理结果所表示的响应上调用
#beforeSend

例如,该测试用例在断言存在
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)
    }
}
打包任何东西
导入com.google.inject.Module
导入groovy.transform.Canonical
导入groovy.transform.compiledDynamic
导入groovy.transform.CompileStatic
导入org.pac4j.core.profile.jwt.jwt索赔
导入org.pac4j.http.client.direct.HeaderClient
导入org.pac4j.jwt.config.encryption.EncryptionConfiguration
导入org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration
导入org.pac4j.jwt.config.signature.SecretSignatureConfiguration
导入org.pac4j.jwt.config.signature.SignatureConfiguration
导入org.pac4j.jwt.credentials.authenticator.JwtAuthenticator
导入org.pac4j.jwt.profile.JwtGenerator
导入org.pac4j.jwt.profile.JwtProfile
导入ratpack.groovy.handling.GroovyChainAction
导入ratpack.groovy.test.handling.GroovyRequestFixture
导入ratpack.guice.guice
导入ratpack.http.Response
导入ratpack.http.Status
导入ratpack.jackson.jackson
导入ratpack.pac4j.RatpackPac4j
导入ratpack.registry.registry
导入ratpack.session.SessionModule
导入ratpack.test.handling.HandlerException NotThrownException
导入ratpack.test.handling.HandlingResult
导入spock.lang.Specification
@编译的
类AuthenticatorTest扩展了规范{
静态字节[]salt=新字节[32]//伪salt
静态SignatureConfiguration SignatureConfiguration=新的SecretSignatureConfiguration(salt)
静态EncryptionConfiguration EncryptionConfiguration=新的加密配置(salt)
静态JwtAuthenticator authenticator=新JwtAuthenticator(签名配置、加密配置)
静态JwtGenerator=新JwtGenerator(签名配置、加密配置)
静态HeaderClient HeaderClient=新的HeaderClient(“授权”、“持有人”、身份验证人)
/**精简的用户类*/
@规范的
静态类用户{
最终字符串id
}
/**精简的用户注册表类*/
@规范的
静态类用户注册表{
专用最终地图用户=[
“joebloggs”:“sekret”
]
用户身份验证(字符串id、字符串密码){
if(密码!=null&&users[id]==password)
返回新用户(id)
返回空
}
}
/**为给定用户生成JWT令牌
*
*@param userId-用户的名称
*@返回编码为字符串的JWT令牌
*/
静态字符串generateToken(字符串用户ID){
JwtProfile profile=新的JwtProfile()
profile.id=userId
profile.addAttribute(JwtClaims.ISSUED_AT,new Date())
字符串标记=generator.generate(配置文件)
代币
}
静态无效传输异常(处理结果){
试一试{
Throwable t=结果。异常(Throwable)
掷t
}
捕获(忽略HandlerException NotThrownException){
}
}
/**组成一个新的注册表绑定传递的模块类
*根据SO的问题https://stackoverflow.com/questions/50814817/how-do-i-mock-a-session-in-ratpack-with-requestfixture
*/

静态注册表addModule(注册表注册表,类迄今为止的最佳答案…或者更严格地说,避开
RequestFixture
限制的一种变通方法是:不要使用
RequestFixture
。使用

(归功于Ratpack松弛通道上的)

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"'
    }
}
打包任何东西
导入com.google.inject.Module
导入groovy.transform.Canonical
导入groovy.transform.compiledDynamic
导入groovy.transform.CompileStatic
导入org.pac4j.core.profile.jwt.jwt索赔
导入org.pac4j.http.client.direct.HeaderClient
导入org.pac4j.jwt.config.encryption.EncryptionConfiguration
导入org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration
导入org.pac4j.jwt.config.signature.SecretSignatureConfiguration
导入org.pac4j.jwt.config.signature.SignatureConfiguration
导入org.pac4j.jwt.credentials.authenticator.JwtAuthenticator
导入org.pac4j.jwt.profile.JwtGenerator
导入org.pac4j.jwt.profile.JwtProfile
导入ratpack.groovy.test.embed.groovyembeddedp
导入ratpack.guice.guice
导入ratpack.http.Response
导入ratpack.http.Status
导入ratpack.http.client.ReceivedResponse
导入ratpack.jackson.jackson
导入ratpack.pac4j.RatpackPac4j
导入ratpack.registry.registry
导入ratpack.session.SessionModule
导入ratpack.test.handling.HandlerException NotThrownException
导入ratpack.test.handli