Merge pull request #250 from Jennifer Strater

* gh-250:
  Polish "Add Grails sample app" (1338df9)
  Add Grails sample app
This commit is contained in:
Andy Wilkinson
2016-05-31 10:01:30 +01:00
21 changed files with 908 additions and 0 deletions

View File

@@ -163,6 +163,10 @@ samples {
dependOn 'spring-restdocs-mockmvc:install'
dependOn 'spring-restdocs-restassured:install'
restNotesGrails {
workingDir "$projectDir/samples/rest-notes-grails"
}
restNotesSpringHateoas {
workingDir "$projectDir/samples/rest-notes-spring-hateoas"
}
@@ -183,6 +187,7 @@ samples {
workingDir "$projectDir/samples/rest-notes-slate"
build false
}
}
task api (type: Javadoc) {

View File

@@ -61,6 +61,9 @@ public class SampleBuildConfigurer {
replaceVersion(new File(this.workingDir, 'build.gradle'),
"ext\\['spring-restdocs.version'\\] = '.*'",
"ext['spring-restdocs.version'] = '${project.version}'")
replaceVersion(new File(this.workingDir, 'build.gradle'),
"restDocsVersion = \".*\"",
"restDocsVersion = \"${project.version}\"")
}
}
else if (new File(sampleDir, 'pom.xml').isFile()) {

View File

@@ -18,6 +18,10 @@ If you want to jump straight in, a number of sample applications are available:
| Gradle
| Demonstrates the use of Spring REST Docs with http://rest-assured.io[REST Assured].
| {samples}/rest-notes-grails[Grails]
| Gradle
| Demonstrates the use of Spring REST docs with https://grails.org[Grails] and https://github.com/spockframework/spock[Spock]
| {samples}/rest-notes-slate[Slate]
| Gradle
| Demonstrates the use of Spring REST Docs with Markdown and

17
samples/rest-notes-grails/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
Thumbs.db
.DS_Store
.gradle
build/
classes/
.idea
*.iml
*.ipr
*.iws
.project
.settings
.classpath
gradlew*
gradle/wrapper
src/docs/generated-snippets

View File

@@ -0,0 +1,2 @@
# grails-spring-restdocs-example
Example of using Spring REST Docs with Grails

View File

@@ -0,0 +1,107 @@
buildscript {
ext {
grailsVersion = project.grailsVersion
}
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
maven { url 'https://repo.spring.io/libs-snapshot' }
}
dependencies {
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
classpath "org.grails.plugins:hibernate:4.3.10.5"
classpath 'org.ajoberstar:gradle-git:1.1.0'
}
}
plugins {
id "io.spring.dependency-management" version "0.5.4.RELEASE"
id 'org.asciidoctor.convert' version '1.5.3'
}
version "0.1"
group "com.example"
apply plugin: "spring-boot"
apply plugin: "war"
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: "org.grails.grails-web"
ext {
grailsVersion = project.grailsVersion
gradleWrapperVersion = project.gradleWrapperVersion
restDocsVersion = "1.1.0.BUILD-SNAPSHOT"
snippetsDir = file('src/docs/generated-snippets')
}
repositories {
mavenLocal()
maven { url 'https://repo.spring.io/libs-snapshot' }
maven { url "https://repo.grails.org/grails/core" }
}
dependencyManagement {
dependencies {
dependency "org.springframework.restdocs:spring-restdocs-restassured:$restDocsVersion"
}
imports {
mavenBom "org.grails:grails-bom:$grailsVersion"
}
applyMavenExclusions false
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-logging"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "org.springframework.boot:spring-boot-autoconfigure"
compile "org.springframework.boot:spring-boot-starter-tomcat"
compile "org.grails:grails-plugin-url-mappings"
compile "org.grails:grails-plugin-rest"
compile "org.grails:grails-plugin-interceptors"
compile "org.grails:grails-plugin-services"
compile "org.grails:grails-plugin-datasource"
compile "org.grails:grails-plugin-databinding"
compile "org.grails:grails-plugin-async"
compile "org.grails:grails-web-boot"
compile "org.grails:grails-logging"
compile "org.grails.plugins:hibernate"
compile "org.grails.plugins:cache"
compile "org.hibernate:hibernate-ehcache"
runtime "com.h2database:h2"
testCompile "org.grails:grails-plugin-testing"
testCompile "org.grails.plugins:geb"
testCompile 'org.springframework.restdocs:spring-restdocs-restassured'
console "org.grails:grails-console"
}
task wrapper(type: Wrapper) {
gradleVersion = gradleWrapperVersion
}
ext {
snippetsDir = file('src/docs/generated-snippets')
}
task cleanTempDirs(type: Delete) {
delete fileTree(dir: 'src/docs/generated-snippets')
}
test {
dependsOn cleanTempDirs
outputs.dir snippetsDir
}
asciidoctor {
dependsOn integrationTest
inputs.dir snippetsDir
sourceDir = file('src/docs')
separateOutputDirs = false
attributes 'snippets': snippetsDir
}
build.dependsOn asciidoctor

View File

@@ -0,0 +1,2 @@
grailsVersion=3.0.15
gradleWrapperVersion=2.3

View File

@@ -0,0 +1,98 @@
---
grails:
profile: web-api
codegen:
defaultPackage: com.example
info:
app:
name: '@info.app.name@'
version: '@info.app.version@'
grailsVersion: '@info.app.grailsVersion@'
spring:
groovy:
template:
check-template-location: false
---
grails:
mime:
disable:
accept:
header:
userAgents:
- Gecko
- WebKit
- Presto
- Trident
types:
all: '*/*'
atom: application/atom+xml
css: text/css
csv: text/csv
form: application/x-www-form-urlencoded
html:
- text/html
- application/xhtml+xml
js: text/javascript
json:
- application/json
- text/json
multipartForm: multipart/form-data
rss: application/rss+xml
text: text/plain
hal:
- application/hal+json
- application/hal+xml
xml:
- text/xml
- application/xml
urlmapping:
cache:
maxsize: 1000
controllers:
defaultScope: singleton
converters:
encoding: UTF-8
hibernate:
cache:
queries: false
---
dataSource:
pooled: true
jmxExport: true
driverClassName: org.h2.Driver
username: sa
password:
environments:
development:
dataSource:
dbCreate: create-drop
url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
test:
dataSource:
dbCreate: update
url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
production:
dataSource:
dbCreate: update
url: jdbc:h2:./prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
properties:
jmxEnabled: true
initialSize: 5
maxActive: 50
minIdle: 5
maxIdle: 25
maxWait: 10000
maxAge: 600000
timeBetweenEvictionRunsMillis: 5000
minEvictableIdleTimeMillis: 60000
validationQuery: SELECT 1
validationQueryTimeout: 3
validationInterval: 15000
testOnBorrow: true
testWhileIdle: true
testOnReturn: false
jdbcInterceptors: ConnectionState
defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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.
*/
import grails.util.BuildSettings
import grails.util.Environment
// See http://logback.qos.ch/manual/groovy.html for details on configuration
appender('STDOUT', ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%level %logger - %msg%n"
}
}
root(ERROR, ['STDOUT'])
def targetDir = BuildSettings.TARGET_DIR
if (Environment.isDevelopmentMode() && targetDir) {
appender("FULL_STACKTRACE", FileAppender) {
file = "${targetDir}/stacktrace.log"
append = true
encoder(PatternLayoutEncoder) {
pattern = "%level %logger - %msg%n"
}
}
logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false)
}

View File

@@ -0,0 +1,2 @@
// Place your Spring DSL code here
beans = {}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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.
*/
class UrlMappings {
static mappings = {
"/"(controller: 'index')
"500"(controller: 'InternalServerError')
"404"(controller: 'NotFound')
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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 com.example
import grails.core.GrailsApplication
import grails.util.Environment
class IndexController {
GrailsApplication grailsApplication
def index() {
render(contentType: 'application/json') {
message = "Welcome to Grails!"
environment = Environment.current.name
appversion = grailsApplication.metadata['info.app.version']
grailsversion = grailsApplication.metadata['info.app.grailsVersion']
appprofile = grailsApplication.config.grails?.profile
groovyversion = GroovySystem.getVersion()
jvmversion = System.getProperty('java.version')
controllers = array {
for (c in grailsApplication.controllerClasses) {
controller([name: c.fullName])
}
}
plugins = array {
for (p in grailsApplication.mainContext.pluginManager.allPlugins) {
plugin([name: p.fullName])
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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 com.example
class InternalServerErrorController {
def index() {
render(contentType: 'application/json') {
error = 500
message = "Internal server error"
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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 com.example
class NotFoundController {
def index() {
render(contentType: 'application/json') {
error = 404
message = "Not Found"
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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 com.example
import grails.rest.Resource
@Resource(uri='/notes', formats = ['json', 'xml'])
class Note {
Long id
String title
String body
static hasMany = [tags: Tag]
static mapping = {
tags joinTable: [name: "mm_notes_tags", key: 'mm_note_id' ]
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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 com.example
import grails.rest.Resource
@Resource(uri='/tags', formats = ['json', 'xml'])
class Tag {
Long id
String name
static hasMany = [notes: Note]
static belongsTo = Note
static mapping = {
notes joinTable: [name: "mm_notes_tags", key: 'mm_tag_id']
}
}

View File

@@ -0,0 +1,56 @@
default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL
default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number
default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address
default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]
default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]
default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}]
default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]
default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maximum size of [{3}]
default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}]
default.invalid.validator.message=Property [{0}] of class [{1}] with value [{2}] does not pass custom validation
default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}]
default.blank.message=Property [{0}] of class [{1}] cannot be blank
default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}]
default.null.message=Property [{0}] of class [{1}] cannot be null
default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique
default.paginate.prev=Previous
default.paginate.next=Next
default.boolean.true=True
default.boolean.false=False
default.date.format=yyyy-MM-dd HH:mm:ss z
default.number.format=0
default.created.message={0} {1} created
default.updated.message={0} {1} updated
default.deleted.message={0} {1} deleted
default.not.deleted.message={0} {1} could not be deleted
default.not.found.message={0} not found with id {1}
default.optimistic.locking.failure=Another user has updated this {0} while you were editing
default.home.label=Home
default.list.label={0} List
default.add.label=Add {0}
default.new.label=New {0}
default.create.label=Create {0}
default.show.label=Show {0}
default.edit.label=Edit {0}
default.button.create.label=Create
default.button.edit.label=Edit
default.button.update.label=Update
default.button.delete.label=Delete
default.button.delete.confirm.message=Are you sure?
# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
typeMismatch.java.net.URL=Property {0} must be a valid URL
typeMismatch.java.net.URI=Property {0} must be a valid URI
typeMismatch.java.util.Date=Property {0} must be a valid Date
typeMismatch.java.lang.Double=Property {0} must be a valid number
typeMismatch.java.lang.Integer=Property {0} must be a valid number
typeMismatch.java.lang.Long=Property {0} must be a valid number
typeMismatch.java.lang.Short=Property {0} must be a valid number
typeMismatch.java.math.BigDecimal=Property {0} must be a valid number
typeMismatch.java.math.BigInteger=Property {0} must be a valid number
typeMismatch=Property {0} is type-mismatched

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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.
*/
import com.example.Note
class BootStrap {
def init = { servletContext ->
environments {
test {
new Note(title: 'Hello, World!', body: 'Hello from the Integration Test').save()
}
}
}
def destroy = {}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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 com.example
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
}

View File

@@ -0,0 +1,148 @@
= Grails RESTful Notes API Guide
Andy Wilkinson; Jenn Strater
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 4
:sectlinks:
[[overview]]
= Overview
[[overview-http-verbs]]
== HTTP verbs
Grails RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
use of HTTP verbs.
|===
| Verb | Usage
| `GET`
| Used to retrieve a resource
| `POST`
| Used to create a new resource
| `PATCH`
| Used to update an existing resource, including partial updates
| `DELETE`
| Used to delete an existing resource
|===
[[overview-http-status-codes]]
== HTTP status codes
Grails RESTful notes tries to adhere as closely as possible to standard HTTP and REST conventions in its
use of HTTP status codes.
|===
| Status code | Usage
| `200 OK`
| The request completed successfully
| `201 Created`
| A new resource has been created successfully. The resource's URI is available from the response's
`Location` header
| `204 No Content`
| An update to an existing resource has been applied successfully
| `400 Bad Request`
| The request was malformed. The response body will include an error providing further information
| `404 Not Found`
| The requested resource did not exist
|===
[[resources]]
= Resources
[[resources-index]]
== Index
The index provides the entry point into the service.
[[resources-index-access]]
=== Accessing the index
A `GET` request is used to access the index
==== Example request
include::{snippets}/index-example/curl-request.adoc[]
==== Response structure
include::{snippets}/index-example/response-fields.adoc[]
==== Example response
include::{snippets}/index-example/http-response.adoc[]
[[resources-notes]]
== Notes
The Notes resources is used to create and list notes
[[resources-notes-list]]
=== Listing notes
A `GET` request will list all of the service's notes.
==== Response structure
include::{snippets}/notes-list-example/response-fields.adoc[]
==== Example request
include::{snippets}/notes-list-example/curl-request.adoc[]
==== Example response
include::{snippets}/notes-list-example/http-response.adoc[]
[[resources-notes-create]]
=== Creating a note
A `POST` request is used to create a note
==== Request structure
include::{snippets}/notes-create-example/request-fields.adoc[]
==== Example request
include::{snippets}/notes-create-example/curl-request.adoc[]
==== Example response
include::{snippets}/notes-create-example/http-response.adoc[]
[[resources-note-retrieve]]
=== Retrieve a note
A `GET` request will retrieve the details of a note
==== Response structure
include::{snippets}/note-get-example/response-fields.adoc[]
==== Example request
include::{snippets}/note-get-example/curl-request.adoc[]
==== Example response
include::{snippets}/note-get-example/http-response.adoc[]

View File

@@ -0,0 +1,164 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed 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
*
* http://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 com.example
import org.springframework.restdocs.payload.JsonFieldType
import static com.jayway.restassured.RestAssured.given
import static org.hamcrest.CoreMatchers.is
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields
import static org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.modifyUris
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration
import com.jayway.restassured.builder.RequestSpecBuilder
import com.jayway.restassured.specification.RequestSpecification
import grails.test.mixin.integration.Integration
import grails.transaction.Rollback
import org.junit.Rule
import org.springframework.restdocs.JUnitRestDocumentation
import org.springframework.http.MediaType
import spock.lang.Specification
@Integration
@Rollback
class ApiDocumentationSpec extends Specification {
@Rule
JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation('src/docs/generated-snippets')
protected RequestSpecification documentationSpec
void setup() {
this.documentationSpec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(restDocumentation))
.build()
}
void 'test and document get request for /index'() {
expect:
given(this.documentationSpec)
.accept(MediaType.APPLICATION_JSON.toString())
.filter(document('index-example',
preprocessRequest(modifyUris()
.host('api.example.com')
.removePort()),
preprocessResponse(prettyPrint()),
responseFields(
fieldWithPath('message').description('Welcome to Grails!'),
fieldWithPath('environment').description("The running environment"),
fieldWithPath('appversion').description('version of the app that is running'),
fieldWithPath('grailsversion').description('the version of grails used in this project'),
fieldWithPath('appprofile').description('the profile of grails used in this project'),
fieldWithPath('groovyversion').description('the version of groovy used in this project'),
fieldWithPath('jvmversion').description('the version of the jvm used in this project'),
fieldWithPath('controllers').type(JsonFieldType.ARRAY).description('the list of available controllers'),
fieldWithPath('plugins').type(JsonFieldType.ARRAY).description('the plugins active for this project'),
)))
.when()
.port(8080)
.get('/')
.then()
.assertThat()
.statusCode(is(200))
}
void 'test and document notes list request'() {
expect:
given(this.documentationSpec)
.accept(MediaType.APPLICATION_JSON.toString())
.filter(document('notes-list-example',
preprocessRequest(modifyUris()
.host('api.example.com')
.removePort()),
preprocessResponse(prettyPrint()),
responseFields(
fieldWithPath('[].class').description('the class of the resource'),
fieldWithPath('[].id').description('the id of the note'),
fieldWithPath('[].title').description('the title of the note'),
fieldWithPath('[].body').description('the body of the note'),
fieldWithPath('[].tags').type(JsonFieldType.ARRAY).description('the list of tags associated with the note'),
)))
.when()
.port(8080)
.get('/notes')
.then()
.assertThat()
.statusCode(is(200))
}
void 'test and document create new note'() {
expect:
given(this.documentationSpec)
.accept(MediaType.APPLICATION_JSON.toString())
.contentType(MediaType.APPLICATION_JSON.toString())
.filter(document('notes-create-example',
preprocessRequest(modifyUris()
.host('api.example.com')
.removePort()),
preprocessResponse(prettyPrint()),
requestFields(
fieldWithPath('title').description('the title of the note'),
fieldWithPath('body').description('the body of the note'),
fieldWithPath('tags').type(JsonFieldType.ARRAY).description('a list of tags associated to the note')
),
responseFields(
fieldWithPath('class').description('the class of the resource'),
fieldWithPath('id').description('the id of the note'),
fieldWithPath('title').description('the title of the note'),
fieldWithPath('body').description('the body of the note'),
fieldWithPath('tags').type(JsonFieldType.ARRAY).description('the list of tags associated with the note'),
)))
.body('{ "body": "My test example", "title": "Eureka!", "tags": [{"name": "testing123"}] }')
.when()
.port(8080)
.post('/notes')
.then()
.assertThat()
.statusCode(is(201))
}
void 'test and document getting specific note'() {
expect:
given(this.documentationSpec)
.accept(MediaType.APPLICATION_JSON.toString())
.filter(document('note-get-example',
preprocessRequest(modifyUris()
.host('api.example.com')
.removePort()),
preprocessResponse(prettyPrint()),
responseFields(
fieldWithPath('class').description('the class of the resource'),
fieldWithPath('id').description('the id of the note'),
fieldWithPath('title').description('the title of the note'),
fieldWithPath('body').description('the body of the note'),
fieldWithPath('tags').type(JsonFieldType.ARRAY).description('the list of tags associated with the note'),
)))
.when()
.port(8080)
.get('/notes/1')
.then()
.assertThat()
.statusCode(is(200))
}
}