/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.grails.web.servlet.mvc

import grails.testing.web.UrlMappingsUnitTest
import org.grails.core.artefact.ControllerArtefactHandler
import org.grails.web.servlet.mvc.alpha.NamespacedController
import grails.web.mapping.mvc.exceptions.CannotRedirectException
import org.grails.web.util.GrailsApplicationAttributes
import grails.artefact.Artefact
import grails.web.mapping.mvc.RedirectEventListener
import org.springframework.http.HttpStatus
import spock.lang.Specification

/**
 * Tests the behaviour of the redirect method.
 *
 * @author Graeme Rocher
 * @since 1.0
 */
class RedirectMethodTests extends Specification implements UrlMappingsUnitTest<UrlMappings> {

    void "test redirect with namespaced controllers"() {
        when:
        def primary = new NamespacedController()
        webRequest.controllerName = 'namespaced'
        primary.redirectToSelf()
        then:
        '/noNamespace/demo' ==  response.redirectedUrl

        when:

        request.removeAttribute(GrailsApplicationAttributes.REDIRECT_ISSUED)
        primary.redirectToSecondary()

        then:
        '/secondaryNamespace/demo' == response.redirectedUrl

        when:
        def secondary = new org.grails.web.servlet.mvc.beta.NamespacedController()
        request.removeAttribute(GrailsApplicationAttributes.REDIRECT_ISSUED)
        secondary.redirectToPrimary()

        then:
        '/noNamespace/demo' == response.redirectedUrl

        when:
        request.removeAttribute(GrailsApplicationAttributes.REDIRECT_ISSUED)
        secondary.redirectToSelfWithImplicitNamespace()

        then:
        '/secondaryNamespace/demo' == response.redirectedUrl

        when:
        request.removeAttribute(GrailsApplicationAttributes.REDIRECT_ISSUED)
        secondary.redirectToSelfWithExplicitNamespace()

        then:
        '/secondaryNamespace/demo' == response.redirectedUrl

        when:
        request.removeAttribute(GrailsApplicationAttributes.REDIRECT_ISSUED)
        secondary.redirectToAnotherPrimary()

        then:
        '/anotherNoNamespace/demo' == response.redirectedUrl

        when:
        request.removeAttribute(GrailsApplicationAttributes.REDIRECT_ISSUED)
        secondary.redirectToAnotherSecondaryWithImplicitNamespace()

        then:
        '/anotherSecondaryNamespace/demo' == response.redirectedUrl

        when:
        request.removeAttribute(GrailsApplicationAttributes.REDIRECT_ISSUED)
        secondary.redirectToAnotherSecondaryWithExplicitNamespace()

        then:
        '/anotherSecondaryNamespace/demo' == response.redirectedUrl
    }

    // chain() issues a real HTTP redirect via response.sendRedirect(), which commits the mock
    // response; a committed MockHttpServletResponse cannot be reused, so each chain case lives in its
    // own feature method to get a fresh response (unlike redirect(), which only sets the Location
    // header and can be exercised repeatedly within a single method).

    void "test chain to the same namespaced controller infers the current namespace"() {
        when:
        def secondary = new org.grails.web.servlet.mvc.beta.NamespacedController()
        webRequest.controllerName = 'namespaced'
        webRequest.controllerNamespace = 'secondary'
        secondary.chainToSelfWithImplicitNamespace()

        then:
        '/secondaryNamespace/demo' == response.redirectedUrl
    }

    void "test chain to a different controller in the same namespace infers the namespace"() {
        given: 'the target namespaced controller is registered so its namespace can be inferred'
        grailsApplication.addArtefact(ControllerArtefactHandler.TYPE, org.grails.web.servlet.mvc.beta.AnotherNamespacedController)
        def linkGenerator = applicationContext.getBean('grailsLinkGenerator')
        linkGenerator.grailsApplication = grailsApplication
        linkGenerator.resetControllerNamespaceCache()

        when:
        def secondary = new org.grails.web.servlet.mvc.beta.NamespacedController()
        webRequest.controllerName = 'namespaced'
        webRequest.controllerNamespace = 'secondary'
        secondary.chainToAnotherSecondaryImplicitNamespace()

        then:
        '/anotherSecondaryNamespace/demo' == response.redirectedUrl
    }

    void "test chain with an explicit namespace is honored"() {
        when:
        def secondary = new org.grails.web.servlet.mvc.beta.NamespacedController()
        webRequest.controllerName = 'namespaced'
        webRequest.controllerNamespace = 'secondary'
        secondary.chainToAnotherSecondaryExplicitNamespace()

        then:
        '/anotherSecondaryNamespace/demo' == response.redirectedUrl
    }

    void "test chain with an explicit null namespace opts out to the root controller"() {
        when:
        def secondary = new org.grails.web.servlet.mvc.beta.NamespacedController()
        webRequest.controllerName = 'namespaced'
        webRequest.controllerNamespace = 'secondary'
        secondary.chainToPrimaryExplicitNullNamespace()

        then:
        '/anotherNoNamespace/demo' == response.redirectedUrl
    }

    void testRedirectToDefaultActionOfAnotherController() {

        when:
        def c = new NewsSignupController()
        webRequest.controllerName = 'newsSignup'
        c.redirectToDefaultAction()

        then:
        "/redirect" ==  response.redirectedUrl
    }

    void testRedirectEventListeners() {
        when:
        def fired = false
        def callable = { fired = true }

        def c = new RedirectController()
        c.setRedirectListeners([new TestRedirectListener(callable: callable)])
        webRequest.controllerName = 'redirect'

        c.toAction()

        then:"redirect event should have been fired"
        fired
    }

    void testRedirectAlreadyCalledException() {

        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'

        c.redirectTwice()

        then:
        def e = thrown(CannotRedirectException)
        e.message ==
        "Cannot issue a redirect(..) here. A previous call to redirect(..) has already redirected the response."

    }

    void testRedirectWhenResponseCommitted() {

        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'

        c.responseCommitted()

        then:"incorrect error message for response redirect when already written to"
        def e = thrown(CannotRedirectException)
        e.message ==
                "Cannot issue a redirect(..) here. The response has already been committed either by another redirect or by directly writing to the response."

    }

    void testRedirectToRoot() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toRoot()
        then:
        "/" == response.redirectedUrl
    }

    void testRedirectToAbsoluteURL() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toAbsolute()
        then:
        "https://www.google.com/" == response.redirectedUrl

    }

    void testRedirectWithFragment() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toControllerAndActionWithFragment()
        then:
        "/test/foo#frag" == response.redirectedUrl
    }

    void testRedirectInControllerWithOneLetterClassName() {
        when:
        def c = new AController()
        webRequest.controllerName = 'a'
        c.index()

        then:
        "/a/list" == response.redirectedUrl
    }

    void testRedirectInControllerWithAllUpperCaseClassName() {
        when:
        def c = new ABCController()
        webRequest.controllerName = 'ABC'
        c.index()
        then:
        "/ABC/list" == response.redirectedUrl
    }

    void testRedirectToAction() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toAction()
        then:
        "/redirect/foo" == response.redirectedUrl
    }

    void testRedirectToActionWithGstring() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toActionWithGstring()
        then:
        "/redirect/foo" == response.redirectedUrl
    }

    void testRedirectToController() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toController()
        then:
        "/test" == response.redirectedUrl
    }

    void testRedirectToControllerAndAction() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toControllerAndAction()
        then:
        "/test/foo" == response.redirectedUrl
    }

    void testRedirectToControllerWithParams() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toControllerWithParams()
        then:
        "/test/foo?one=two&two=three" == response.redirectedUrl
    }

    void testRedirectToControllerWithDuplicateParams() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toControllerWithDuplicateParams()
        then:
        "/test/foo?one=two&one=three" == response.redirectedUrl
    }

    void testRedirectToControllerWithDuplicateArrayParams() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toControllerWithDuplicateArrayParams()
        then:
        "/test/foo?one=two&one=three" == response.redirectedUrl
    }

    void testRedirectToActionWithMapping() {
        when:
        def c = new NewsSignupController()
        c = new NewsSignupController()
        webRequest.controllerName = 'newsSignup'
        c.testNoController()
        then:
        "/little-brown-bottle/thankyou" ==  response.redirectedUrl
    }

    void "test temporary redirect"() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toActionTemporaryRedirect()
        
        then: "should use HTTP 307 Temporary Redirect"
        HttpStatus.TEMPORARY_REDIRECT.value() == response.status
        "/redirect/foo" == response.redirectedUrl
    }

    void "test permanent redirect"() {
        when:
        def c = new RedirectController() 
        webRequest.controllerName = 'redirect'
        c.toActionPermanentRedirect()
        
        then: "should use HTTP 308 Permanent Redirect"
        HttpStatus.PERMANENT_REDIRECT.value() == response.status
        "/redirect/foo" == response.redirectedUrl
    }

    void "test moved temporary redirect"() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toActionMovedTemporary()

        then: "should use HTTP 302 Found (formerly Moved Temporarily)"
        HttpStatus.FOUND.value() == response.status
        "/redirect/foo" == response.redirectedUrl
    }

    void "test moved permanent redirect"() {
        when:
        def c = new RedirectController()
        webRequest.controllerName = 'redirect'
        c.toActionMovedPermanent()

        then: "should use HTTP 301 Moved Permanent"
        HttpStatus.MOVED_PERMANENTLY.value() == response.status
        "/redirect/foo" == response.redirectedUrl
    }

    static class UrlMappings {
        static mappings = {
            "/"(controller:'default')
            "/little-brown-bottle/$action?" {
                controller = "newsSignup"
            }

            "/$controller/$action?/$id?"{
                constraints {
                    // apply constraints here
                }
            }
            "/noNamespace/$action?" {
                controller = 'namespaced'
            }
            "/anotherNoNamespace/$action?" {
                controller = 'anotherNamespaced'
            }

            "/secondaryNamespace/$action?" {
                controller = 'namespaced'
                namespace = 'secondary'
            }
            "/anotherSecondaryNamespace/$action?" {
                controller = 'anotherNamespaced'
                namespace = 'secondary'
            }
        }
    }
}

class TestRedirectListener implements RedirectEventListener {

    def callable

    void responseRedirected(String url) {
        callable(url)
    }
}

@Artefact('Controller')
class ABCController {
    def index = { redirect action: 'list' }
}

@Artefact('Controller')
class AController {
    def index = { redirect action: 'list' }
}

@Artefact('Controller')
class NewsSignupController {

    static defaultAction = "thankyou"

    def testNoController = {
        redirect(action: 'thankyou')
    }

    def redirectToDefaultAction = {
        redirect(controller:"redirect")
    }

    def thankyou = {
    }
}
