From 4ff1abbeafdbddd351857c5516c0fce335f352ec Mon Sep 17 00:00:00 2001 From: tpluscode Date: Thu, 25 Jul 2019 13:40:24 +0200 Subject: [PATCH] feat: setting up request messages adds the possibility to set request headers and body fix #32 fix #42 --- .../app/hypermedia/testing/dsl/Core.xtext | 34 +++- .../app/hypermedia/testing/dsl/Hydra.xtext | 6 +- .../CoreValueConverterService.xtend | 5 + .../conversion/MultilineBlockConverter.xtend | 25 +++ .../dsl/generator/HydraGenerator.xtend | 24 ++- .../dsl/tests/generator/hydra/InvokeTest.snap | 174 ++++++++++++++++++ .../tests/generator/hydra/InvokeTest.xtend | 112 +++++++++++ .../dsl/tests/hydra/InvokeParsingTest.xtend | 117 ++++++++++++ .../dsl/tests/hydra/StatusParsingTest.xtend | 2 +- 9 files changed, 492 insertions(+), 7 deletions(-) create mode 100644 app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/conversion/MultilineBlockConverter.xtend create mode 100644 app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/generator/hydra/InvokeTest.snap create mode 100644 app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/generator/hydra/InvokeTest.xtend create mode 100644 app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/hydra/InvokeParsingTest.xtend diff --git a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/Core.xtext b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/Core.xtext index 45be18c..8a8f41d 100644 --- a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/Core.xtext +++ b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/Core.xtext @@ -70,7 +70,7 @@ LinkStatement: ; HeaderStatement: - ExpectModifier 'Header' fieldName=(ID | HYPHENATED_NAME) (exactValue=STRING | regex=MatchingRegex | variable=VARIABLE)? + ExpectModifier 'Header' fieldName=FieldName (exactValue=STRING | regex=MatchingRegex | variable=VARIABLE)? ; MatchingRegex: @@ -81,5 +81,37 @@ FollowStatement: 'Follow' variable=VARIABLE ; +RequestBlock: + {RequestBlock} '{' + (headers+=RequestHeader)* + (body=RequestBody)? + '}' +; + +ResponseBlock: + {ResponseBlock} '{' + (children+=ResponseStep)* + '}' +; + +RequestBody: + contents=MULTILINE_BLOCK | + reference=RequestFileBody +; + +RequestFileBody: + '<<<' path=STRING +; + +RequestHeader: + fieldName=FieldName value=STRING +; + +FieldName: (ID | HYPHENATED_NAME); + terminal VARIABLE: '[' ID ']'; terminal HYPHENATED_NAME: ('a'..'z' | 'A'..'Z') ('-' | 'a'..'z' | 'A'..'Z')*; + +terminal MULTILINE_BLOCK: + '```' -> '```' +; diff --git a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/Hydra.xtext b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/Hydra.xtext index c0aafb5..7878213 100644 --- a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/Hydra.xtext +++ b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/Hydra.xtext @@ -39,13 +39,11 @@ OperationBlock: ; InvocationBlock: - {InvocationBlock} 'Invoke' '{' - (children+=ResponseStep)* - '}' + 'Invoke' (request=RequestBlock '=>')? response=ResponseBlock ; terminal URI: - '<' ( '\\' . /* 'b'|'t'|'n'|'f'|'r'|'u'|'"'|"'"|'\\' */ | !('\\'|'>') )* '>' + '<' ( '\\' . /* 'b'|'t'|'n'|'f'|'r'|'u'|'"'|"'"|'\\' */ | !('\\'|'>'|'<') )* '>' ; terminal PREFIX: ID ':'; terminal QNAME: PREFIX (INT | ID | '/' | '%')+; diff --git a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/conversion/CoreValueConverterService.xtend b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/conversion/CoreValueConverterService.xtend index 0d120cc..9d5be36 100644 --- a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/conversion/CoreValueConverterService.xtend +++ b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/conversion/CoreValueConverterService.xtend @@ -10,6 +10,11 @@ class CoreValueConverterService extends DefaultTerminalConverters { return new VariableReferenceConverter() } + @ValueConverter(rule = "MULTILINE_BLOCK") + def IValueConverter getMultilineBlockConverter() { + return new MultilineBlockConverter() + } + @ValueConverter(rule = "PREFIX") def IValueConverter getPrefixConverter() { return new PrefixConverter() diff --git a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/conversion/MultilineBlockConverter.xtend b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/conversion/MultilineBlockConverter.xtend new file mode 100644 index 0000000..30f500e --- /dev/null +++ b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/conversion/MultilineBlockConverter.xtend @@ -0,0 +1,25 @@ +package app.hypermedia.testing.dsl.conversion + +import org.eclipse.xtext.conversion.IValueConverter +import org.eclipse.xtext.conversion.ValueConverterException +import org.eclipse.xtext.nodemodel.INode + +class MultilineBlockConverter implements IValueConverter { + final String INVALID_VALUE_ERROR = "Code block must be between triple backticks" + + override toString(String value) { + return value + } + + override toValue(String string, INode node) throws ValueConverterException { + if (string === null) { + return null + } + + if(string.length < 7 || !string.startsWith("```") || !string.endsWith("```")) { + throw new ValueConverterException(INVALID_VALUE_ERROR, node, null) + } + + return string.substring(3, string.length - 3).trim() + } +} \ No newline at end of file diff --git a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/generator/HydraGenerator.xtend b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/generator/HydraGenerator.xtend index 0cf6b39..bc8bc1c 100644 --- a/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/generator/HydraGenerator.xtend +++ b/app.hypermedia.testing.dsl/src/main/java/app/hypermedia/testing/dsl/generator/HydraGenerator.xtend @@ -7,6 +7,7 @@ import app.hypermedia.testing.dsl.hydra.OperationBlock import app.hypermedia.testing.dsl.hydra.InvocationBlock import app.hypermedia.testing.dsl.Modifier import java.util.HashMap +import org.json.JSONObject import app.hypermedia.testing.dsl.hydra.UriName import app.hypermedia.testing.dsl.hydra.PrefixedName import org.eclipse.xtext.generator.IFileSystemAccess2 @@ -51,7 +52,28 @@ final Map _namespaces def dispatch step(InvocationBlock it) { val map = new HashMap - return buildBlock('Invocation', children, map) + if(request !== null) { + if(request.headers.length > 0) { + val headers = new JSONObject + request.headers.forEach[header | + headers.put(header.fieldName, header.value) + ] + + map.put('headers', headers) + } + + if(request.body !== null) { + if (request.body.reference !== null) { + val body = new JSONObject() + body.put('path', request.body.reference.path) + map.put('body', body) + } else { + map.put('body', request.body.contents) + } + } + } + + return buildBlock('Invocation', response.children, map) } def dispatch identifier(UriName it) { diff --git a/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/generator/hydra/InvokeTest.snap b/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/generator/hydra/InvokeTest.snap new file mode 100644 index 0000000..473549c --- /dev/null +++ b/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/generator/hydra/InvokeTest.snap @@ -0,0 +1,174 @@ +app.hypermedia.testing.dsl.tests.generator.hydra.InvokeTest.classOperation_invokeTwiceWithRequests_generatesSteps=[ + { + "map": { + "steps": { + "myArrayList": [ + { + "map": { + "children": { + "myArrayList": [ + { + "map": { + "children": { + "myArrayList": [ + { + "map": { + "body": { + "map": { + "path": "/some/path/data.csv" + }, + "empty": false + }, + "children": { + "myArrayList": [ + { + "map": { + "code": 201, + "type": "ResponseStatus" + }, + "empty": false + } + ], + "empty": false + }, + "headers": { + "map": { + "Content-Type": "text/csv" + }, + "empty": false + }, + "type": "Invocation" + }, + "empty": false + }, + { + "map": { + "body": "{\n \"hello\": \"world\",\n \"foo\": \"bar\"\n }", + "children": { + "myArrayList": [ + { + "map": { + "code": 409, + "type": "ResponseStatus" + }, + "empty": false + } + ], + "empty": false + }, + "headers": { + "map": { + "Content-Type": "application/json" + }, + "empty": false + }, + "type": "Invocation" + }, + "empty": false + } + ], + "empty": false + }, + "operationId": "http://example.com/CreateUser", + "strict": false, + "type": "Operation" + }, + "empty": false + } + ], + "empty": false + }, + "classId": "http://example.com/People", + "type": "Class" + }, + "empty": false + } + ], + "empty": false + } + }, + "empty": false + } +] + + +app.hypermedia.testing.dsl.tests.generator.hydra.InvokeTest.topLevelOperation_invokeTwiceWithRequests_generatesSteps=[ + { + "map": { + "steps": { + "myArrayList": [ + { + "map": { + "children": { + "myArrayList": [ + { + "map": { + "body": { + "map": { + "path": "/some/path/data.csv" + }, + "empty": false + }, + "children": { + "myArrayList": [ + { + "map": { + "code": 201, + "type": "ResponseStatus" + }, + "empty": false + } + ], + "empty": false + }, + "headers": { + "map": { + "Content-Type": "text/csv" + }, + "empty": false + }, + "type": "Invocation" + }, + "empty": false + }, + { + "map": { + "body": "{\n \"hello\": \"world\",\n \"foo\": \"bar\"\n }", + "children": { + "myArrayList": [ + { + "map": { + "code": 409, + "type": "ResponseStatus" + }, + "empty": false + } + ], + "empty": false + }, + "headers": { + "map": { + "Content-Type": "application/json" + }, + "empty": false + }, + "type": "Invocation" + }, + "empty": false + } + ], + "empty": false + }, + "operationId": "http://example.com/CreateUser", + "strict": false, + "type": "Operation" + }, + "empty": false + } + ], + "empty": false + } + }, + "empty": false + } +] \ No newline at end of file diff --git a/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/generator/hydra/InvokeTest.xtend b/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/generator/hydra/InvokeTest.xtend new file mode 100644 index 0000000..ab87b46 --- /dev/null +++ b/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/generator/hydra/InvokeTest.xtend @@ -0,0 +1,112 @@ +package app.hypermedia.testing.dsl.tests.generator.hydra + +import org.eclipse.xtext.generator.InMemoryFileSystemAccess +import org.junit.jupiter.api.^extension.ExtendWith +import org.eclipse.xtext.testing.extensions.InjectionExtension +import org.eclipse.xtext.testing.InjectWith +import com.google.inject.Inject +import org.eclipse.xtext.testing.util.ParseHelper +import app.hypermedia.testing.dsl.hydra.HydraScenario +import org.junit.jupiter.api.Test +import static io.github.jsonSnapshot.SnapshotMatcher.* +import org.eclipse.xtext.generator.IGenerator2 +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.AfterAll +import org.json.JSONObject +import app.hypermedia.testing.dsl.tests.HydraInjectorProvider +import org.eclipse.xtext.generator.GeneratorContext + +@ExtendWith(InjectionExtension) +@InjectWith(HydraInjectorProvider) +class InvokeTest { + @Inject IGenerator2 generator + @Inject extension ParseHelper + + @BeforeAll + static def beforeAll() { + start() + } + + @AfterAll + static def afterAll() { + validateSnapshots(); + } + + @Test + def topLevelOperation_invokeTwiceWithRequests_generatesSteps() { + // given + val model = ''' + With Operation { + Invoke { + Content-Type "text/csv" + + <<< "/some/path/data.csv" + } => { + Expect Status 201 + } + + Invoke { + Content-Type "application/json" + + ``` + { + "hello": "world", + "foo": "bar" + } + ``` + } => { + Expect Status 409 + } + } + '''.parse + + // when + val fsa = new InMemoryFileSystemAccess() + generator.doGenerate(model.eResource, fsa, new GeneratorContext()) + println(fsa.textFiles) + + // then + val file = new JSONObject(fsa.textFiles.values.get(0).toString) + expect(file).toMatchSnapshot() + } + + @Test + def classOperation_invokeTwiceWithRequests_generatesSteps() { + // given + val model = ''' + With Class { + With Operation { + Invoke { + Content-Type "text/csv" + + <<< "/some/path/data.csv" + } => { + Expect Status 201 + } + + Invoke { + Content-Type "application/json" + + ``` + { + "hello": "world", + "foo": "bar" + } + ``` + } => { + Expect Status 409 + } + } + } + '''.parse + + // when + val fsa = new InMemoryFileSystemAccess() + generator.doGenerate(model.eResource, fsa, new GeneratorContext()) + println(fsa.textFiles) + + // then + val file = new JSONObject(fsa.textFiles.values.get(0).toString) + expect(file).toMatchSnapshot() + } +} \ No newline at end of file diff --git a/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/hydra/InvokeParsingTest.xtend b/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/hydra/InvokeParsingTest.xtend new file mode 100644 index 0000000..71b8b66 --- /dev/null +++ b/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/hydra/InvokeParsingTest.xtend @@ -0,0 +1,117 @@ +/* + * generated by Xtext 2.18.0 + */ +package app.hypermedia.testing.dsl.tests.hydra + +import app.hypermedia.testing.dsl.hydra.HydraScenario +import com.google.inject.Inject +import org.eclipse.xtext.testing.InjectWith +import org.eclipse.xtext.testing.extensions.InjectionExtension +import org.eclipse.xtext.testing.util.ParseHelper +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.^extension.ExtendWith +import app.hypermedia.testing.dsl.tests.HydraInjectorProvider +import static org.assertj.core.api.Assertions.* +import app.hypermedia.testing.dsl.tests.TestHelpers +import app.hypermedia.testing.dsl.hydra.OperationBlock +import app.hypermedia.testing.dsl.core.StatusStatement + +@ExtendWith(InjectionExtension) +@InjectWith(HydraInjectorProvider) +class InvokeParsingTest { + @Inject extension + ParseHelper parseHelper + + @Test + def void invokeWithHeaders_parsesHeadersSuccessfully() { + // when + val result = parseHelper.parse(''' + With Operation { + Invoke { + Content-Type "text/turtle" + ETag 'W/""' + } => { + Expect Status 405 + } + } + ''') + + // then + TestHelpers.assertModelParsedSuccessfully(result) + + val operationBlock = result.steps.get(0) as OperationBlock + val invocation = operationBlock.invocations.get(0) + assertThat(invocation.request.headers).hasSize(2) + assertThat(invocation.request.headers.get(0).fieldName).isEqualTo('Content-Type') + assertThat(invocation.request.headers.get(1).fieldName).isEqualTo('ETag') + assertThat(invocation.request.headers.get(0).value).isEqualTo('text/turtle') + assertThat(invocation.request.headers.get(1).value).isEqualTo('W/""') + } + + @Test + def void invokeWithInlineBody_parsesBodyContentsSuccessfully() { + // when + val result = parseHelper.parse(''' + With Operation { + Invoke { + ``` + <> a api:Person . + ``` + } => { + Expect Status 405 + } + } + ''') + + // then + TestHelpers.assertModelParsedSuccessfully(result) + + val operationBlock = result.steps.get(0) as OperationBlock + val invocation = operationBlock.invocations.get(0) + assertThat(invocation.request.body.contents).contains('<> a api:Person .') + } + + @Test + def void invokeWithBodyFromFile_parsesPathSuccessfully() { + // when + val result = parseHelper.parse(''' + With Operation { + Invoke { + <<< "../../sample-bodies/data.csv" + } => { + Expect Status 405 + } + } + ''') + + // then + TestHelpers.assertModelParsedSuccessfully(result) + + val operationBlock = result.steps.get(0) as OperationBlock + val invocation = operationBlock.invocations.get(0) + assertThat(invocation.request.body.reference.path).isEqualTo('../../sample-bodies/data.csv') + } + + @Test + def void invokeWithBodyFromFile_parsesResponeBlock() { + // when + val result = parseHelper.parse(''' + With Operation { + Invoke { + <<< "../../sample-bodies/data.csv" + } => { + Expect Status 405 + } + } + ''') + + // then + TestHelpers.assertModelParsedSuccessfully(result) + + val operationBlock = result.steps.get(0) as OperationBlock + val invocation = operationBlock.invocations.get(0) + assertThat(invocation.response.children).hasSize(1) + val statusStatement = invocation.response.children.get(0) as StatusStatement + assertThat(statusStatement.status).isEqualTo(405) + } +} diff --git a/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/hydra/StatusParsingTest.xtend b/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/hydra/StatusParsingTest.xtend index 6457639..3dc0e00 100644 --- a/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/hydra/StatusParsingTest.xtend +++ b/app.hypermedia.testing.dsl/src/test/java/app/hypermedia/testing/dsl/tests/hydra/StatusParsingTest.xtend @@ -43,7 +43,7 @@ class StatusParsingTest { val operationBlock = result.steps.get(0) as OperationBlock val invocationBlock = operationBlock.invocations.get(0) as InvocationBlock - val statusBlock = invocationBlock.children.get(0) as StatusStatement + val statusBlock = invocationBlock.response.children.get(0) as StatusStatement assertThat(statusBlock.status).isEqualTo(status) }