jdaugherty commented on PR #16272:
URL: https://github.com/apache/grails-core/pull/16272#issuecomment-5878127455

   **Suggested functional tests: resource links under the hyphenated URL 
converter, in `hyphenated`**
   
   Could you add these to this PR? `HyphenatedLinkResolutionSpec` adds 
`TourGuide`, served by `CityGuidesController` in the default namespace, which 
also has a `showDetails` action, and by `TourDeskController` and 
`GuideLedgerController` in `backOffice`. It pins links from a page in the 
default namespace, links from each `backOffice` controller's own page, a 
multi-word action, and the redirect after a form save.
   
   At `5c4195c5a9`, 3 of the 7 fail: the links rendered by `TourDeskController` 
and `GuideLedgerController`, and the save redirect, reach 
`CityGuidesController` (`/city-guides/show/2` after saving on the tour desk). 
Under this converter the request's `controllerName` is the URL form 
(`tour-desk`), while the controller index holds `tourDesk`, so the controller 
handling the request is never recognised as a candidate. With that comparison 
also made against `grailsUrlConverter.toUrlElement(candidate.name)`, all 7 pass 
in a local run. All 7 fail on 8.0.x, which links to `/tour-guide/...`.
   
   The expectations pin the namespace segment as it is generated, 
`/backOffice/...`. That form routes, as `/back-office/...` does.
   
   Apply from the repository root with `git apply`; it applies cleanly to 
`5c4195c5a9`.
   
   <details><summary><code>hyphenated</code> patch (8 files)</summary>
   
   ```diff
   diff --git a/grails-test-examples/hyphenated/build.gradle 
b/grails-test-examples/hyphenated/build.gradle
   index b4ea82debf..ec2ccd6416 100644
   --- a/grails-test-examples/hyphenated/build.gradle
   +++ b/grails-test-examples/hyphenated/build.gradle
   @@ -70,6 +70,7 @@ dependencies {
    
    
        integrationTestImplementation 
testFixtures('org.apache.grails:grails-geb')
   +    integrationTestImplementation 
'org.apache.grails:grails-testing-support-http-client'
    }
    
    apply {
   diff --git 
a/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/CityGuidesController.groovy
 
b/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/CityGuidesController.groovy
   new file mode 100644
   index 0000000000..4f2aefb288
   --- /dev/null
   +++ 
b/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/CityGuidesController.groovy
   @@ -0,0 +1,32 @@
   +/*
   + *  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 hyphenated
   +
   +import grails.rest.RestfulController
   +
   +class CityGuidesController extends RestfulController<TourGuide> {
   +
   +    CityGuidesController() {
   +        super(TourGuide)
   +    }
   +
   +    def showDetails(Long id) {
   +        render "City guide details ${id}"
   +    }
   +}
   diff --git 
a/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/LinkPageController.groovy
 
b/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/LinkPageController.groovy
   new file mode 100644
   index 0000000000..ca4db9b098
   --- /dev/null
   +++ 
b/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/LinkPageController.groovy
   @@ -0,0 +1,26 @@
   +/*
   + *  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 hyphenated
   +
   +class LinkPageController {
   +
   +    def links(Long guideId) {
   +        render view: '/links/guideLinks', model: [guide: 
TourGuide.get(guideId)]
   +    }
   +}
   diff --git 
a/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/backoffice/GuideLedgerController.groovy
 
b/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/backoffice/GuideLedgerController.groovy
   new file mode 100644
   index 0000000000..9accbf0d71
   --- /dev/null
   +++ 
b/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/backoffice/GuideLedgerController.groovy
   @@ -0,0 +1,35 @@
   +/*
   + *  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 hyphenated.backoffice
   +
   +import grails.rest.RestfulController
   +import hyphenated.TourGuide
   +
   +class GuideLedgerController extends RestfulController<TourGuide> {
   +
   +    static namespace = 'backOffice'
   +
   +    GuideLedgerController() {
   +        super(TourGuide)
   +    }
   +
   +    def links(Long guideId) {
   +        render view: '/links/guideLinks', model: [guide: 
TourGuide.get(guideId)]
   +    }
   +}
   diff --git 
a/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/backoffice/TourDeskController.groovy
 
b/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/backoffice/TourDeskController.groovy
   new file mode 100644
   index 0000000000..3efaa7759b
   --- /dev/null
   +++ 
b/grails-test-examples/hyphenated/grails-app/controllers/hyphenated/backoffice/TourDeskController.groovy
   @@ -0,0 +1,35 @@
   +/*
   + *  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 hyphenated.backoffice
   +
   +import grails.rest.RestfulController
   +import hyphenated.TourGuide
   +
   +class TourDeskController extends RestfulController<TourGuide> {
   +
   +    static namespace = 'backOffice'
   +
   +    TourDeskController() {
   +        super(TourGuide)
   +    }
   +
   +    def links(Long guideId) {
   +        render view: '/links/guideLinks', model: [guide: 
TourGuide.get(guideId)]
   +    }
   +}
   diff --git 
a/grails-test-examples/hyphenated/grails-app/domain/hyphenated/TourGuide.groovy 
b/grails-test-examples/hyphenated/grails-app/domain/hyphenated/TourGuide.groovy
   new file mode 100644
   index 0000000000..e1de873ba6
   --- /dev/null
   +++ 
b/grails-test-examples/hyphenated/grails-app/domain/hyphenated/TourGuide.groovy
   @@ -0,0 +1,24 @@
   +/*
   + *  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 hyphenated
   +
   +class TourGuide {
   +
   +    String name
   +}
   diff --git 
a/grails-test-examples/hyphenated/grails-app/views/links/guideLinks.gsp 
b/grails-test-examples/hyphenated/grails-app/views/links/guideLinks.gsp
   new file mode 100644
   index 0000000000..eee6fbac84
   --- /dev/null
   +++ b/grails-test-examples/hyphenated/grails-app/views/links/guideLinks.gsp
   @@ -0,0 +1,30 @@
   +<%--
   +  ~  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.
   +  --%>
   +<%@ page import="hyphenated.TourGuide" %>
   +<!doctype html>
   +<html>
   +<head>
   +    <title>Guide Links</title>
   +</head>
   +<body>
   +<a id="guideShowLink" href="${createLink(resource: guide, action: 
'show')}">Guide</a>
   +<a id="guideDetailsLink" href="${createLink(resource: guide, action: 
'showDetails')}">Guide details</a>
   +<a id="guideDetailsByClassLink" href="${createLink(resource: TourGuide, 
action: 'showDetails', id: guide.id)}">Guide details, by class</a>
   +</body>
   +</html>
   diff --git 
a/grails-test-examples/hyphenated/src/integration-test/groovy/hyphenated/HyphenatedLinkResolutionSpec.groovy
 
b/grails-test-examples/hyphenated/src/integration-test/groovy/hyphenated/HyphenatedLinkResolutionSpec.groovy
   new file mode 100644
   index 0000000000..6f054d4afa
   --- /dev/null
   +++ 
b/grails-test-examples/hyphenated/src/integration-test/groovy/hyphenated/HyphenatedLinkResolutionSpec.groovy
   @@ -0,0 +1,92 @@
   +/*
   + *  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 hyphenated
   +
   +import java.net.http.HttpClient
   +import java.util.regex.Matcher
   +import java.util.regex.Pattern
   +
   +import spock.lang.Shared
   +import spock.lang.Specification
   +import spock.lang.Tag
   +import spock.lang.Unroll
   +
   +import grails.testing.mixin.integration.Integration
   +import org.apache.grails.testing.http.client.HttpClientSupport
   +import org.apache.grails.testing.http.client.TestHttpResponse
   +
   +/**
   + * Pins where resource links land when the hyphenated URL converter renames 
controllers, actions and
   + * namespaces in URLs.
   + */
   +@Integration(applicationClass = Application)
   +@Tag('http-client')
   +class HyphenatedLinkResolutionSpec extends Specification implements 
HttpClientSupport {
   +
   +    @Shared
   +    Long guideId
   +
   +    void setup() {
   +        if (!guideId) {
   +            TourGuide.withNewTransaction {
   +                guideId = new TourGuide(name: 'Rome').save(flush: true, 
failOnError: true).id
   +            }
   +        }
   +    }
   +
   +    @Unroll
   +    void "#element rendered by #page targets #expected"(String page, String 
element, String expected) {
   +        when:
   +        String target = 
linkTarget(http("${page}?guideId=${guideId}").body() as String, element)
   +
   +        then:
   +        target == expected.replace('{id}', String.valueOf(guideId))
   +
   +        where: 'a namespace is generated in its logical form, which routes 
as the hyphenated one does'
   +        page                               | element                   || 
expected
   +        '/link-page/links'                 | 'guideShowLink'           || 
'/city-guides/show/{id}'
   +        '/link-page/links'                 | 'guideDetailsLink'        || 
'/city-guides/show-details/{id}'
   +        '/link-page/links'                 | 'guideDetailsByClassLink' || 
'/city-guides/show-details/{id}'
   +        '/back-office/tour-desk/links'     | 'guideShowLink'           || 
'/backOffice/tour-desk/show/{id}'
   +        '/back-office/tour-desk/links'     | 'guideDetailsLink'        || 
'/city-guides/show-details/{id}'
   +        '/back-office/guide-ledger/links'  | 'guideShowLink'           || 
'/backOffice/guide-ledger/show/{id}'
   +    }
   +
   +    void "a form save on a namespaced controller not named after the domain 
redirects to it"() {
   +        given:
   +        HttpClient client = newHttpClientWith { 
followRedirects(HttpClient.Redirect.NEVER) }
   +
   +        when:
   +        TestHttpResponse response = httpPost([:], 
'/back-office/tour-desk/save', 'name=Paris',
   +                'application/x-www-form-urlencoded', client)
   +        String location = new URI(response.headerValue('Location') ?: 
'').path
   +
   +        then:
   +        response.statusCode() == 302
   +        location ==~ Pattern.quote('/backOffice/tour-desk/show/') + /\d+/
   +    }
   +
   +    private static String linkTarget(String html, String element) {
   +        Matcher tag = html =~ 
/<a\b[^>]*\bid="${Pattern.quote(element)}"[^>]*>/
   +        assert tag.find(), "no element with id ${element}"
   +        Matcher attribute = tag.group() =~ /\bhref="([^"]*)"/
   +        assert attribute.find()
   +        attribute.group(1).replace('&amp;', '&')
   +    }
   +}
   ```
   
   </details>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to