Search code examples
grailsintellij-ideacasfunctional-testinggeb

How to do functional test for a Grails 3 app with redirection after CAS login?


Problem

I have an app that uses CAS (Central Authentication Service) login. In the configuration (application.yml), CAS is configured as follows:

grails:   
  plugin:
    springsecurity:
      # ...other configurations omitted ...
      cas:
         serverUrlPrefix: "https://<some endpoint for the actual CAS>"
         serviceUrl: https://localhost:8443/myapp/login/cas
         # ...other configurations omitted ...

When running functional tests, a Tomcat server is initiated with random port number, e.g. http://localhost:45359 and it is always changing with each run.

It seems like the app gets redirected to the CAS serviceUrl after login, and normally my app runs on port 8443, so it's never a problem. But since the functional test brings up the Tomcat server at random port number, the redirection fails.

Also, I'm running my app using run-app -https (i.e. https://localhost:8443), but the test runs on http (e.g. http://localhost:12345). I don't know if this is an important detail or not.

The Test

My functional test goes as follows:

  1. Navigate to the app's main URL, which gets redirected to the login page
  2. Check the title for the login page
  3. Enters the login credentials
  4. Click the Submit button
  5. Errors with "Connection Refused" when it tries to connect to localhost:8443.

Source (based on: https://github.com/grails-guides/grails-test-security/blob/master/complete/src/integration-test/groovy/grails/test/security/AnnouncementControllerSpec.groovy):

@Integration
class MyAppControllerFunctionalSpec extends GebSpec {

    def setup(){
    }

    def cleanup() {
    }

    void "test something"() {
        when:"The home page is visited"
            go '/myapp'  // 1

        then:"The title is correct"
            title == "Login Page"  // 2
            at LoginPage // a helper class - subclass of geb.Page.
                         // Contains implementation of login method

        when: "Sign in"
            login('username', 'password') // 3 & 4 --> 5

        then: "Title"
            title == "Welcome MyApp"
}

Stacktrace:

java.lang.RuntimeException: org.apache.http.conn.HttpHostConnectException: Connect to localhost:8443 [localhost/127.0.0.1] failed: Connection refused (Connection refused)

    at com.gargoylesoftware.htmlunit.WebClient.download(WebClient.java:2067)
    at com.gargoylesoftware.htmlunit.html.HtmlForm.submit(HtmlForm.java:138)
    at com.gargoylesoftware.htmlunit.html.HtmlButton.doClickStateUpdate(HtmlButton.java:97)
    at com.gargoylesoftware.htmlunit.html.DomElement.click(DomElement.java:786)
    at com.gargoylesoftware.htmlunit.html.DomElement.click(DomElement.java:733)
    at org.openqa.selenium.htmlunit.HtmlUnitMouse.click(HtmlUnitMouse.java:74)
    at org.openqa.selenium.htmlunit.HtmlUnitWebElement.click(HtmlUnitWebElement.java:151)
    at geb.navigator.NonEmptyNavigator.click(NonEmptyNavigator.groovy:437)
    at geb.content.TemplateDerivedPageContent.click(TemplateDerivedPageContent.groovy:115)
    at myapp.LoginPage.login(LoginPage.groovy:22)
    at geb.Browser.methodMissing(Browser.groovy:207)
    at geb.spock.GebSpec.methodMissing(GebSpec.groovy:56)
    at myapp.MyAppControllerFunctionalSpec.test something(MyAppControllerFunctionalSpec.groovy:39)
Caused by: org.apache.http.conn.HttpHostConnectException: Connect to localhost:8443 [localhost/127.0.0.1] failed: Connection refused (Connection refused)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:158)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:71)
    at com.gargoylesoftware.htmlunit.HttpWebConnection.getResponse(HttpWebConnection.java:179)
    at com.gargoylesoftware.htmlunit.WebClient.loadWebResponseFromWebConnection(WebClient.java:1321)
    at com.gargoylesoftware.htmlunit.WebClient.loadWebResponseFromWebConnection(WebClient.java:1379)
    at com.gargoylesoftware.htmlunit.WebClient.loadWebResponse(WebClient.java:1238)
    at com.gargoylesoftware.htmlunit.WebClient.download(WebClient.java:2063)
    ... 12 more
Caused by: java.net.ConnectException: Connection refused (Connection refused)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:337)
    at com.gargoylesoftware.htmlunit.httpclient.HtmlUnitSSLConnectionSocketFactory.connectSocket(HtmlUnitSSLConnectionSocketFactory.java:189)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141)
    ... 25 more

Question

What is the correct way to make it so my functional tests can get past the CAS login?

Other Info

  • Grails: 3.2.7
  • Groovy: 2.4.12
  • IntelliJ: 2017.1.5

UPDATE

I have tried setting port numbers using an environment variable, to no avail. See: this Grails isssue


Solution

  • My current solution to get the functional test to run with CAS login from Intellij IDEA:

    1. Make sure the test class is under the src/integration-test/groovy/<package>/ directory
    2. Run/launch the app using the Run commands or control buttons. Don't run the app from the IDE's Terminal.
    3. Run the test from the IDE's Terminal:

      grails test-app package.test_class --integration

    For example, with package: foo and test class MyAppControllerFunctionalSpec:

    grails test-app foo.MyAppControllerFunctionalSpec --integration