Skip to content

Commit a67f030

Browse files
committed
Added tests for additional coverage
1 parent 8e419ef commit a67f030

File tree

8 files changed

+296
-16
lines changed

8 files changed

+296
-16
lines changed

codecov.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ comment:
3838
ignore:
3939
- "/springfox-grails/src/main/java/springfox/documentation/grails/SynthesizedAnnotations.java"
4040
- "/springfox-grails/src/main/java/springfox/documentation/grails/ActionSpecificationFactory.java"
41+
- "/springfox-grails/src/main/java/springfox/documentation/grails/SpringfoxGrailsIntegrationConfiguration.java"

springfox-grails/src/main/java/springfox/documentation/grails/GrailsRequestHandler.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.fasterxml.classmate.ResolvedType;
44
import com.google.common.base.Optional;
5-
import grails.core.GrailsDomainClass;
65
import org.springframework.core.annotation.AnnotationUtils;
76
import org.springframework.http.MediaType;
87
import org.springframework.web.bind.annotation.PathVariable;
@@ -14,6 +13,7 @@
1413
import springfox.documentation.RequestHandler;
1514
import springfox.documentation.RequestHandlerKey;
1615
import springfox.documentation.service.ResolvedMethodParameter;
16+
import springfox.documentation.spring.web.plugins.CombinedRequestHandler;
1717

1818
import java.lang.annotation.Annotation;
1919
import java.util.ArrayList;
@@ -23,6 +23,8 @@
2323
import java.util.Set;
2424
import java.util.stream.Collectors;
2525

26+
import static org.springframework.util.StringUtils.capitalize;
27+
2628
class GrailsRequestHandler implements RequestHandler {
2729
private final GrailsActionContext actionContext;
2830
private final GrailsActionAttributes urlProvider;
@@ -70,11 +72,13 @@ public String groupName() {
7072

7173
@Override
7274
public String getName() {
73-
GrailsDomainClass domainClass = actionContext.getDomainClass();
74-
if (domainClass != null) {
75-
return String.format("%s%s", actionContext.getAction(), domainClass.getName());
76-
}
77-
return actionContext.getAction();
75+
return Optional.fromNullable(actionContext.getDomainClass())
76+
.transform(domain ->
77+
String.format(
78+
"%s%s",
79+
actionContext.getAction(),
80+
capitalize(domain.getLogicalPropertyName())))
81+
.or(actionContext.getAction());
7882
}
7983

8084
@Override
@@ -141,7 +145,7 @@ public RequestMappingInfo getRequestMapping() {
141145

142146
@Override
143147
public RequestHandler combine(RequestHandler other) {
144-
throw new UnsupportedOperationException();
148+
return new CombinedRequestHandler(this, other);
145149
}
146150

147151
}

springfox-grails/src/test/groovy/springfox/documentation/grails/ActionSpecificationResolverSpec.groovy

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,19 @@ import spock.lang.Unroll
1212

1313
class ActionSpecificationResolverSpec extends Specification {
1414
def resolver = new TypeResolver()
15-
def controller = Mock(GrailsControllerClass)
15+
def restfulController = Mock(GrailsControllerClass)
16+
def regularController = Mock(GrailsControllerClass)
1617
def domain = Mock(GrailsDomainClass)
1718
def identifierProperty = Mock(GrailsDomainClassProperty)
1819
def links = Mock(LinkGenerator)
1920
def urlMappings = Mock(UrlMappings)
2021
GrailsActionAttributes actionAttributes
2122

2223
def setup() {
23-
controller.clazz >> AController
24-
controller.name >> "A"
24+
regularController.clazz >> BookController
25+
regularController.name >> "Book"
26+
restfulController.clazz >> AController
27+
restfulController.name >> "A"
2528
domain.clazz >> ADomain
2629
domain.identifier >> identifierProperty
2730
domain.identifier.type >> Integer
@@ -37,14 +40,31 @@ class ActionSpecificationResolverSpec extends Specification {
3740
and:
3841
urlMappings.urlMappings >> [otherMapping(Mock(UrlMapping))]
3942
when:
40-
def spec = sut.resolve(new GrailsActionContext(controller, domain, actionAttributes, action))
43+
def spec = sut.resolve(new GrailsActionContext(restfulController, domain, actionAttributes, action))
4144
then:
4245
spec != null
4346
spec.handlerMethod.method.name == action
4447
where:
4548
action << ["index", "save", "show", "edit", "update", "delete", "patch", "create", "other"]
4649
}
4750

51+
@Unroll
52+
def "Resolves action #action on non-restful controller"() {
53+
given:
54+
def sut = new ActionSpecificationResolver(
55+
new RestfulActionSpecificationFactory(resolver),
56+
new MethodBackedActionSpecificationFactory(resolver, actionAttributes))
57+
and:
58+
urlMappings.urlMappings >> [otherMapping(Mock(UrlMapping))]
59+
when:
60+
def spec = sut.resolve(new GrailsActionContext(regularController, null, actionAttributes, action))
61+
then:
62+
spec != null
63+
spec.handlerMethod.method.name == action
64+
where:
65+
action << ["index", "save", "show", "edit", "update", "delete", "create"]
66+
}
67+
4868
def otherMapping(UrlMapping urlMapping) {
4969
urlMapping.controllerName >> "A"
5070
urlMapping.actionName >> "other"

springfox-grails/src/test/groovy/springfox/documentation/grails/ActionsSpec.groovy

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import grails.core.GrailsControllerClass
44
import grails.core.GrailsDomainClass
55
import grails.web.mapping.LinkGenerator
66
import grails.web.mapping.UrlMappings
7+
import org.springframework.http.MediaType
78
import org.springframework.web.bind.annotation.RequestMethod
89
import spock.lang.Specification
910
import spock.lang.Unroll
1011

11-
1212
class ActionsSpec extends Specification implements GrailsControllerSupport {
1313
def "Cannot instantiate this class"() {
1414
when:
@@ -65,6 +65,21 @@ class ActionsSpec extends Specification implements GrailsControllerSupport {
6565
OverridenController | "noOverrides" | [RequestMethod.GET] as Set
6666
}
6767

68+
@Unroll
69+
def "Detects grails produces overrides for #controller"() {
70+
given:
71+
def grailsController = grailsController(controller)
72+
def context = context(grailsController, "any")
73+
when:
74+
def methods = Actions.producesOverrides(context)
75+
then:
76+
methods == expected
77+
where:
78+
controller | expected
79+
AController | [MediaType.APPLICATION_JSON] as Set
80+
OverridenController | [MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML] as Set
81+
}
82+
6883
def grailsController(Class controller) {
6984
def grails = Mock(GrailsControllerClass)
7085
grails.clazz >> controller
@@ -87,7 +102,8 @@ class ActionsSpec extends Specification implements GrailsControllerSupport {
87102

88103
class OverridenController {
89104
static allowedMethods = [withOverrides: "POST", update: "PUT", delete: "DELETE"]
90-
105+
static responseFormats = ['json', 'xml']
106+
91107
def withOverrides() {
92108
}
93109

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package springfox.documentation.grails
2+
3+
4+
class Book {
5+
Long id
6+
String name
7+
8+
static constraints = {
9+
name nullable: false
10+
}
11+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package springfox.documentation.grails
2+
3+
4+
import grails.transaction.Transactional
5+
import grails.web.Action
6+
import io.swagger.annotations.ApiImplicitParam
7+
import io.swagger.annotations.ApiImplicitParams
8+
import io.swagger.annotations.ApiOperation
9+
import springfox.documentation.annotations.ApiIgnore
10+
11+
import static org.springframework.http.HttpStatus.*
12+
13+
class BookController {
14+
15+
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
16+
17+
@Action
18+
@ApiOperation(value = "index", httpMethod = "GET", notes = "Creates a book")
19+
def index(Integer max) {
20+
params.max = Math.min(max ?: 10, 100)
21+
respond Book.list(params), model: [bookCount: Book.count()]
22+
}
23+
24+
@Action
25+
@ApiIgnore
26+
def show(Book book) {
27+
respond book
28+
}
29+
30+
@Action
31+
@ApiOperation(value = "create", httpMethod = "POST", notes = "Creates a book")
32+
@ApiImplicitParams([
33+
@ApiImplicitParam(name = "name", dataType = "string", required = true, paramType = "form",
34+
value = "Name of the book")
35+
])
36+
def create() {
37+
respond new Book(params)
38+
}
39+
40+
@Action
41+
@Transactional
42+
@ApiOperation(value = "save", httpMethod = "POST", notes = "Saves a book")
43+
def save(Book book) {
44+
if (book == null) {
45+
transactionStatus.setRollbackOnly()
46+
notFound()
47+
return
48+
}
49+
50+
if (book.hasErrors()) {
51+
transactionStatus.setRollbackOnly()
52+
respond book.errors, view: 'create'
53+
return
54+
}
55+
56+
book.save flush: true
57+
58+
request.withFormat {
59+
form multipartForm {
60+
flash.message = message(code: 'default.created.message', args: [message(code: 'book.label', default: 'Book'), book.id])
61+
redirect book
62+
}
63+
'*' { respond book, [status: CREATED] }
64+
}
65+
}
66+
67+
@Action
68+
@ApiIgnore
69+
def edit(Book book) {
70+
respond book
71+
}
72+
73+
@Action
74+
@Transactional
75+
@ApiOperation(value = "update", httpMethod = "PUT", notes = "Updates a book")
76+
def update(Book book) {
77+
if (book == null) {
78+
transactionStatus.setRollbackOnly()
79+
notFound()
80+
return
81+
}
82+
83+
if (book.hasErrors()) {
84+
transactionStatus.setRollbackOnly()
85+
respond book.errors, view: 'edit'
86+
return
87+
}
88+
89+
book.save flush: true
90+
91+
request.withFormat {
92+
form multipartForm {
93+
flash.message = message(code: 'default.updated.message', args: [message(code: 'book.label', default: 'Book'), book.id])
94+
redirect book
95+
}
96+
'*' { respond book, [status: OK] }
97+
}
98+
}
99+
100+
@Action
101+
@Transactional
102+
@ApiOperation(value = "delete", httpMethod = "DELETE", notes = "Deletes a book")
103+
@ApiImplicitParams([
104+
@ApiImplicitParam(name = "id", dataType = "long", required = true, paramType = "form",
105+
value = "Id of the book")
106+
])
107+
def delete(Book book) {
108+
109+
if (book == null) {
110+
transactionStatus.setRollbackOnly()
111+
notFound()
112+
return
113+
}
114+
115+
book.delete flush: true
116+
117+
request.withFormat {
118+
form multipartForm {
119+
flash.message = message(code: 'default.deleted.message', args: [message(code: 'book.label', default: 'Book'), book.id])
120+
redirect action: "index", method: "GET"
121+
}
122+
'*' { render status: NO_CONTENT }
123+
}
124+
}
125+
126+
protected void notFound() {
127+
request.withFormat {
128+
form multipartForm {
129+
flash.message = message(code: 'default.not.found.message', args: [message(code: 'book.label', default: 'Book'), params.id])
130+
redirect action: "index", method: "GET"
131+
}
132+
'*' { render status: NOT_FOUND }
133+
}
134+
}
135+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package springfox.documentation.grails
2+
3+
import com.fasterxml.classmate.TypeResolver
4+
import grails.core.GrailsApplication
5+
import grails.core.GrailsControllerClass
6+
import grails.core.GrailsDomainClass
7+
import grails.web.mapping.LinkGenerator
8+
import grails.web.mapping.UrlMapping
9+
import grails.web.mapping.UrlMappings
10+
import spock.lang.Specification
11+
12+
class GrailsRequestHandlerProviderSpec extends Specification {
13+
def "Integration test" () {
14+
given:
15+
def resolver = new TypeResolver()
16+
def attributes = attributes()
17+
def application = application()
18+
and:
19+
def sut = new GrailsRequestHandlerProvider(
20+
application,
21+
attributes,
22+
new ActionSpecificationResolver(
23+
new RestfulActionSpecificationFactory(resolver),
24+
new MethodBackedActionSpecificationFactory(resolver, attributes)
25+
),
26+
new DefaultGrailsAlternateTypeRuleConvention(resolver, application,
27+
new GrailsSerializationTypeGenerator(
28+
new DefaultGrailsPropertySelector(),
29+
new DefaultGrailsPropertyTransformer(),
30+
new DefaultGeneratedClassNamingStrategy())
31+
)
32+
)
33+
expect:
34+
sut.requestHandlers().size() == 1
35+
}
36+
37+
UrlMappings urlMappings() {
38+
def mappings = Mock(UrlMappings)
39+
mappings.urlMappings >> [mapping()]
40+
mappings
41+
}
42+
43+
def mapping() {
44+
def mapping = Mock(UrlMapping)
45+
mapping.actionName >> "create"
46+
mapping.httpMethod >> "POST"
47+
mapping.controllerName >> "BookController"
48+
mapping
49+
}
50+
51+
LinkGenerator linkGenerator() {
52+
def links = Mock(LinkGenerator)
53+
links.link(_) >> "/test"
54+
links.serverBaseURL >> "http://localhost:8080"
55+
links
56+
}
57+
58+
GrailsActionAttributes attributes() {
59+
new GrailsActionAttributes(
60+
linkGenerator(),
61+
urlMappings())
62+
}
63+
64+
GrailsApplication application() {
65+
def application = Mock(GrailsApplication)
66+
application.getArtefacts("Controller") >> [bookController()]
67+
application.getArtefacts("Domain") >> [bookDomain()]
68+
application
69+
}
70+
71+
def bookDomain() {
72+
def domain = Mock(GrailsDomainClass)
73+
domain.logicalPropertyName >> "Book"
74+
domain.name >> "book"
75+
domain.clazz >> Book
76+
domain
77+
}
78+
79+
def bookController() {
80+
def controller = Mock(GrailsControllerClass)
81+
controller.name >> "BookController"
82+
controller.logicalPropertyName >> "Book"
83+
controller.clazz >> BookController
84+
controller.actions >> ["create"]
85+
controller
86+
}
87+
}

0 commit comments

Comments
 (0)