This is an automated email from the ASF dual-hosted git repository. jamesfredley pushed a commit to branch test/artefact-name-precomputation in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 2af11ac8dfedb59d90e3ef7b975aa113484b3ffa Author: James Fredley <[email protected]> AuthorDate: Fri Jul 17 14:23:48 2026 -0400 Add artefact name contract characterization spec Assisted-by: opencode:gpt-5.6-sol --- .../core/ArtefactNamePrecomputationSpec.groovy | 213 +++++++++++++++++++++ 1 file changed, 213 insertions(+) diff --git a/grails-core/src/test/groovy/org/grails/core/ArtefactNamePrecomputationSpec.groovy b/grails-core/src/test/groovy/org/grails/core/ArtefactNamePrecomputationSpec.groovy new file mode 100644 index 0000000000..4c383dea6d --- /dev/null +++ b/grails-core/src/test/groovy/org/grails/core/ArtefactNamePrecomputationSpec.groovy @@ -0,0 +1,213 @@ +/* + * 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.core + +import grails.core.GrailsClass +import spock.lang.Specification +import spock.lang.Unroll + +/** + * Tests for deterministic GrailsClass naming metadata. + */ +class ArtefactNamePrecomputationSpec extends Specification { + + private static final String BASE_PACKAGE = 'org.grails.core' + + @Unroll + void "naming contract for #entry.label is precomputed"() { + given: + GrailsClass grailsClass = grailsClassFor(entry.artifactType, entry.wrapperClass) + + expect: + grailsClass.name == entry.name + grailsClass.shortName == entry.shortName + grailsClass.fullName == entry.fullName + grailsClass.packageName == entry.packageName + grailsClass.propertyName == entry.propertyName + grailsClass.logicalPropertyName == entry.logicalPropertyName + grailsClass.naturalName == entry.naturalName + + where: + entry << [ + [ + label: 'Acronym controller', + artifactType: 'controller', + wrapperClass: HTMLController, + name: 'HTML', + shortName: 'HTMLController', + fullName: "${BASE_PACKAGE}.HTMLController", + packageName: BASE_PACKAGE, + propertyName: 'HTMLController', + logicalPropertyName: 'HTML', + naturalName: 'HTMLC ontroller' + ], + [ + label: 'Camel mixed controller', + artifactType: 'controller', + wrapperClass: PayRollController, + name: 'PayRoll', + shortName: 'PayRollController', + fullName: "${BASE_PACKAGE}.PayRollController", + packageName: BASE_PACKAGE, + propertyName: 'payRollController', + logicalPropertyName: 'payRoll', + naturalName: 'Pay Roll Controller' + ], + [ + label: 'Acronym service', + artifactType: 'service', + wrapperClass: JSONAPIService, + name: 'JSONAPI', + shortName: 'JSONAPIService', + fullName: "${BASE_PACKAGE}.JSONAPIService", + packageName: BASE_PACKAGE, + propertyName: 'JSONAPIService', + logicalPropertyName: 'JSONAPI', + naturalName: 'JSONAPIS ervice' + ], + [ + label: 'Acronym domain', + artifactType: 'domain', + wrapperClass: URLDomain, + name: 'URLDomain', + shortName: 'URLDomain', + fullName: "${BASE_PACKAGE}.URLDomain", + packageName: BASE_PACKAGE, + propertyName: 'URLDomain', + logicalPropertyName: 'URLDomain', + naturalName: 'URLD omain' + ], + [ + label: 'Url mappings artefact', + artifactType: 'urlMappings', + wrapperClass: HomeUrlMappings, + name: 'Home', + shortName: 'HomeUrlMappings', + fullName: "${BASE_PACKAGE}.HomeUrlMappings", + packageName: BASE_PACKAGE, + propertyName: 'homeUrlMappings', + logicalPropertyName: 'home', + naturalName: 'Home Url Mappings' + ] + ] + } + + @Unroll + void "naming accessors are stable on repeated read for #entry.label"() { + given: + GrailsClass grailsClass = grailsClassFor(entry.artifactType, entry.wrapperClass) + + expect: + 10.times { + assert grailsClass.name == entry.name + assert grailsClass.shortName == entry.shortName + assert grailsClass.fullName == entry.fullName + assert grailsClass.packageName == entry.packageName + assert grailsClass.propertyName == entry.propertyName + assert grailsClass.logicalPropertyName == entry.logicalPropertyName + assert grailsClass.naturalName == entry.naturalName + } + + where: + entry << [ + [ + label: 'Acronym controller', + artifactType: 'controller', + wrapperClass: HTMLController, + name: 'HTML', + shortName: 'HTMLController', + fullName: "${BASE_PACKAGE}.HTMLController", + packageName: BASE_PACKAGE, + propertyName: 'HTMLController', + logicalPropertyName: 'HTML', + naturalName: 'HTMLC ontroller' + ], + [ + label: 'Acronym service', + artifactType: 'service', + wrapperClass: JSONAPIService, + name: 'JSONAPI', + shortName: 'JSONAPIService', + fullName: "${BASE_PACKAGE}.JSONAPIService", + packageName: BASE_PACKAGE, + propertyName: 'JSONAPIService', + logicalPropertyName: 'JSONAPI', + naturalName: 'JSONAPIS ervice' + ] + ] + } + + @Unroll + void "naming is stable across wrapper construction for #entry.label"() { + expect: + GrailsClass first = grailsClassFor(entry.artifactType, entry.wrapperClass) + GrailsClass second = grailsClassFor(entry.artifactType, entry.wrapperClass) + first.name == second.name + first.shortName == second.shortName + first.fullName == second.fullName + first.packageName == second.packageName + first.propertyName == second.propertyName + first.logicalPropertyName == second.logicalPropertyName + first.naturalName == second.naturalName + + where: + entry << [ + [ + label: 'Camel mixed controller', + artifactType: 'controller', + wrapperClass: PayRollController + ], + [ + label: 'Url mappings artefact', + artifactType: 'urlMappings', + wrapperClass: HomeUrlMappings + ], + [ + label: 'Acronym domain', + artifactType: 'domain', + wrapperClass: URLDomain + ] + ] + } + + private GrailsClass grailsClassFor(String artifactType, Class<?> wrapperClass) { + switch (artifactType) { + case 'controller': + return new DefaultGrailsControllerClass(wrapperClass) + case 'service': + return new DefaultGrailsServiceClass(wrapperClass) + case 'domain': + return new DefaultGrailsDomainClass(wrapperClass) + case 'urlMappings': + return new DefaultGrailsUrlMappingsClass(wrapperClass) + default: + throw new IllegalArgumentException("Unknown artifact type [${artifactType}]") + } + } +} + +class HTMLController {} + +class PayRollController {} + +class JSONAPIService {} + +class URLDomain {} + +class HomeUrlMappings {}
