springgrailsspring-securitygrails-rest-api

Secured users created in grails integration test are unauthorized but bootstrapped ones are


I'm using Grails Spring Security Core and the Grails Spring Security REST plugin and I'm just starting to get things set up. I initialized the plugins with a User class and an Authority class (defaults) and went to write an integration test, following a guide I found on the Grails website.

It said to put the following in an integration test:

def "test a user with the role ROLE_BOSS is able to access /api/announcements url"() {
    when: 'login with the sherlock'
    RestBuilder rest = new RestBuilder()
    def resp = rest.post("http://localhost:${serverPort}/api/login") { 
        accept('application/json')
        contentType('application/json')
        json {
            username = 'sherlock'
            password = 'elementary'
        }
    }

    then:
    resp.status == 200
    resp.json.roles.find { it == 'ROLE_BOSS' }
}

I went ahead and did something similar and it worked with a bootstrapped User, but when I tried to do the exact same test with a User created in the test method itself, it would fail with a 401 HTTP response code.

The code I'm trying to run:

void "check get access token"() {
    given:
    RestBuilder rest = new RestBuilder()
    new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
    assert User.count == 2

    when:
    def resp = rest.post("http://localhost:${serverPort}/api/login") {
        accept('application/json')
        contentType('application/json')
        json {
            username = "securitySpecTestUserName"
            password = "securitySpecTestPassword"
        }
    }

    then:
    resp.status == 200
}

Note that the User.count == 2 assertion passes because there is one User in Bootstrap.groovy and the one create in the test method.

Why does this work and pass with the bootstrapped User without any issues at all but not the one created in the method? Is there a way I can write this integration test so that I can test the /api/login endpoint included in the grails-spring-security-rest plugin in this way?


Solution

  • The User you create in the given section is in a transaction that has not been committed. When you make the REST call, the api/login controller will be run in a new transaction that cannot see your un-committed User.

    A few options (there are others)...

    1. Create User in BootStrap.groovy

      def init = { servletContext ->
        environments {
          test {
            new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
          }
        }
      }
      
    2. Make REST calls to create the User - assuming you have such functionality

    3. Create User in setup

      @Integration
      @Rollback
      class UserIntSpec extends Specification {
      
        def setup() {
          new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
        }
      
        void "check get access token"() {
          given:
          RestBuilder rest = new RestBuilder()
      
          when:
          def response = rest.post("http://localhost:${serverPort}/api/login") {
            accept('application/json')
            contentType('application/json')
            json {
              username = "securitySpecTestUserName"
              password = "securitySpecTestPassword"
            }
          }
      
         then:
         response.status == HttpServletResponse.SC_OK
      
         when:
         def token = response.json.access_token
      
         then:
         token
        }
      }
      

    Note: In Grails >= 3.0, setup() is run in a separate transaction and persisted (why it solves your problem) which is not rolled back. Any data will need to be cleaned up manually.

    I suggest you read the grails documentation on testing: Integration Testing