├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── user-story.md └── workflows │ └── main.yml ├── .gitignore ├── .gitlab-ci.yml ├── .gitlab ├── issue_templates │ ├── bug_report.md │ └── user_story.md └── merge_request_templates │ └── merge_request.md ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── encodings.xml ├── kotlinScripting.xml ├── kotlinc.xml └── vcs.xml ├── .ideainspect ├── CHANGELOG.adoc ├── LICENSE ├── QuickStart.adoc ├── README.adoc ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── Versions.kt │ ├── java-project-config.gradle.kts │ └── kotlin-project-config.gradle.kts ├── detekt-config.yml ├── doc ├── README.adoc ├── decisions │ ├── README.adoc │ ├── adr-001-keep-architecture-decision-records.adoc │ ├── adr-002-use-asciidoc-markup.adoc │ ├── adr-003-use-gradle-as-build-tool.adoc │ ├── adr-004-implement-junit5-engine.adoc │ └── adr-005-use-java-spi.adoc ├── glossary.adoc └── quality-goals.adoc ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── livingdoc-api ├── livingdoc-api.gradle.kts └── src │ └── main │ └── java │ └── org │ └── livingdoc │ └── api │ ├── After.java │ ├── Before.java │ ├── conversion │ ├── ConversionException.java │ ├── Converter.java │ ├── Format.java │ ├── Language.java │ └── TypeConverter.java │ ├── disabled │ └── Disabled.java │ ├── documents │ ├── ExecutableDocument.java │ ├── FailFast.java │ └── Group.java │ ├── exception │ └── ExampleSyntax.java │ ├── fixtures │ ├── decisiontables │ │ ├── AfterRow.java │ │ ├── BeforeFirstCheck.java │ │ ├── BeforeRow.java │ │ ├── Check.java │ │ ├── DecisionTableFixture.java │ │ └── Input.java │ └── scenarios │ │ ├── Binding.java │ │ ├── ScenarioFixture.java │ │ └── Step.java │ └── tagging │ ├── Tag.java │ └── Tags.java ├── livingdoc-config ├── livingdoc-config.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── config │ │ ├── ConfigProvider.kt │ │ └── YamlUtils.kt │ └── test │ ├── kotlin │ └── org │ │ └── livingdoc │ │ └── config │ │ └── ConfigProviderTest.kt │ └── resources │ └── test-config.yaml ├── livingdoc-converters ├── livingdoc-converters.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── livingdoc │ │ │ └── converters │ │ │ ├── BooleanConverter.kt │ │ │ ├── CharacterConverter.kt │ │ │ ├── JSONConverter.kt │ │ │ ├── StringConverter.kt │ │ │ ├── TypeConverterManager.kt │ │ │ ├── TypeConverters.kt │ │ │ ├── collection │ │ │ ├── AbstractCollectionConverter.kt │ │ │ ├── ListConverter.kt │ │ │ ├── MapConverter.kt │ │ │ ├── SetConverter.kt │ │ │ └── Tokenizer.kt │ │ │ ├── color │ │ │ └── ColorConverter.kt │ │ │ ├── exceptions │ │ │ ├── ColorFormatException.kt │ │ │ ├── MalformedFormatException.kt │ │ │ ├── NumberRangeException.kt │ │ │ └── ValueFormatException.kt │ │ │ ├── number │ │ │ ├── AbstractNumberConverter.kt │ │ │ ├── BigDecimalConverter.kt │ │ │ ├── BigIntegerConverter.kt │ │ │ ├── ByteConverter.kt │ │ │ ├── DoubleConverter.kt │ │ │ ├── FloatConverter.kt │ │ │ ├── IntegerConverter.kt │ │ │ ├── LongConverter.kt │ │ │ └── ShortConverter.kt │ │ │ └── time │ │ │ ├── AbstractTemporalConverter.kt │ │ │ ├── LocalDateConverter.kt │ │ │ ├── LocalDateTimeConverter.kt │ │ │ ├── LocalTimeConverter.kt │ │ │ ├── OffsetDateTimeConverter.kt │ │ │ └── ZonedDateTimeConverter.kt │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ └── org.livingdoc.api.conversion.TypeConverter │ │ └── properties │ │ └── color.properties │ └── test │ ├── java │ └── org │ │ └── livingdoc │ │ └── converters │ │ ├── BooleanConverterJavaTest.java │ │ ├── CharacterConverterJavaTest.java │ │ ├── StringConverterJavaTest.java │ │ ├── TypeConvertersTestFixtures.java │ │ ├── collection │ │ ├── ListConverterJavaTest.java │ │ ├── MapConverterJavaTest.java │ │ └── SetConverterJavaTest.java │ │ ├── number │ │ ├── BigDecimalConverterJavaTest.java │ │ ├── BigIntegerConverterJavaTest.java │ │ ├── ByteConverterJavaTest.java │ │ ├── DoubleConverterJavaTest.java │ │ ├── FloatConverterJavaTest.java │ │ ├── IntegerConverterJavaTest.java │ │ ├── LongConverterJavaTest.java │ │ └── ShortConverterJavaTest.java │ │ └── time │ │ ├── LocalDateConverterJavaTest.java │ │ ├── LocalDateTimeConverterJavaTest.java │ │ ├── LocalTimeConverterJavaTest.java │ │ ├── OffsetDateTimeConverterJavaTest.java │ │ └── ZonedDateTimeConverterJavaTest.java │ ├── kotlin │ ├── org │ │ └── livingdoc │ │ │ └── converters │ │ │ ├── BooleanConverterTest.kt │ │ │ ├── CharacterConverterTest.kt │ │ │ ├── DefaultTypeConverterContract.kt │ │ │ ├── JSONConverterTest.kt │ │ │ ├── StringConverterTest.kt │ │ │ ├── TypeConvertersTest.kt │ │ │ ├── collection │ │ │ ├── CollectionConverterContract.kt │ │ │ ├── ListConverterTest.kt │ │ │ ├── MapConverterTest.kt │ │ │ └── SetConverterTest.kt │ │ │ ├── color │ │ │ └── ColorConverterTest.kt │ │ │ ├── number │ │ │ ├── BigDecimalConverterTest.kt │ │ │ ├── BigIntegerConverterTest.kt │ │ │ ├── BoundedNumberConverterContract.kt │ │ │ ├── ByteConverterTest.kt │ │ │ ├── DoubleConverterTest.kt │ │ │ ├── FloatConverterTest.kt │ │ │ ├── IntegerConverterTest.kt │ │ │ ├── LongConverterTest.kt │ │ │ ├── NumberConverterContract.kt │ │ │ └── ShortConverterTest.kt │ │ │ └── time │ │ │ ├── LocalDateConverterTest.kt │ │ │ ├── LocalDateTimeConverterTest.kt │ │ │ ├── LocalTimeConverterTest.kt │ │ │ ├── OffsetDateTimeConverterTest.kt │ │ │ ├── TemporalConverterContract.kt │ │ │ └── ZonedDateTimeConverterTest.kt │ └── utils │ │ ├── EnglishDefaultLocale.kt │ │ └── EnglishDefaultLocaleExtension.kt │ └── resources │ ├── colorData.csv │ └── invalidColorData.csv ├── livingdoc-documentation ├── livingdoc-documentation.gradle.kts └── src │ ├── docs │ └── asciidoc │ │ ├── annotate-tests.adoc │ │ ├── business-expert │ │ ├── creating-testdata.adoc │ │ ├── index.adoc │ │ ├── pic │ │ │ ├── MANUAL-in-fixture.png │ │ │ ├── execution-report-confluence-page.PNG │ │ │ ├── execution-report-confluence-result.PNG │ │ │ ├── scenario-final-view.png │ │ │ ├── table-multiple-columns.png │ │ │ └── view.png │ │ ├── test-reports.adoc │ │ └── writing-testdata.adoc │ │ ├── configuration.adoc │ │ ├── fixtures-decision-tables.adoc │ │ ├── fixtures-scenarios.adoc │ │ ├── fixtures.adoc │ │ ├── index.adoc │ │ ├── reports.adoc │ │ └── repositories.adoc │ └── main │ └── java │ ├── examples │ ├── decisiontables │ │ └── CalculatorFixture.java │ └── scenarios │ │ └── CalculatorFixture.java │ └── implementations │ └── Calculator.java ├── livingdoc-engine ├── livingdoc-engine.gradle.kts └── src │ ├── jmh │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── engine │ │ └── execution │ │ └── examples │ │ └── decisiontables │ │ ├── AdditionDecisionTableBenchmarks.kt │ │ ├── BaselineBenchmark.kt │ │ └── CoinSumsDecisionTableBenchmarks.kt │ ├── main │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── engine │ │ ├── DecisionTableToFixtureMatcher.kt │ │ ├── LivingDoc.kt │ │ ├── ScenarioToFixtureMatcher.kt │ │ ├── algo │ │ └── Stemmer.kt │ │ ├── config │ │ ├── TaggingConfig.kt │ │ └── TaggingDefinition.kt │ │ ├── execution │ │ ├── ExecutionException.kt │ │ ├── FixtureChecks.kt │ │ ├── MalformedFixtureException.kt │ │ ├── ScopedFixtureModel.kt │ │ ├── documents │ │ │ ├── DocumentExecution.kt │ │ │ ├── DocumentFixture.kt │ │ │ ├── DocumentFixtureChecker.kt │ │ │ ├── DocumentFixtureModel.kt │ │ │ └── DocumentIdentifier.kt │ │ ├── examples │ │ │ ├── NoExpectedExceptionThrownException.kt │ │ │ ├── Util.kt │ │ │ ├── decisiontables │ │ │ │ ├── DecisionTableFixtureChecker.kt │ │ │ │ ├── DecisionTableFixtureModel.kt │ │ │ │ ├── DecisionTableFixtureWrapper.kt │ │ │ │ └── DecisionTableNoFixture.kt │ │ │ └── scenarios │ │ │ │ ├── ScenarioFixtureChecker.kt │ │ │ │ ├── ScenarioFixtureModel.kt │ │ │ │ ├── ScenarioFixtureWrapper.kt │ │ │ │ ├── ScenarioNoFixture.kt │ │ │ │ └── matching │ │ │ │ ├── MatchingFunctions.kt │ │ │ │ ├── RegMatching.kt │ │ │ │ ├── ScenarioStepMatcher.kt │ │ │ │ ├── StemmerHandler.kt │ │ │ │ └── StepTemplate.kt │ │ └── groups │ │ │ ├── GroupExecution.kt │ │ │ ├── GroupFixture.kt │ │ │ ├── GroupFixtureChecker.kt │ │ │ ├── GroupFixtureModel.kt │ │ │ └── ImplicitGroup.kt │ │ └── fixtures │ │ ├── Fixture.kt │ │ ├── FixtureFieldInjector.kt │ │ └── FixtureMethodInvoker.kt │ └── test │ ├── java │ └── org │ │ └── livingdoc │ │ └── engine │ │ ├── DecisionTableToFixtureMatcherTest.kt │ │ └── execution │ │ ├── documents │ │ └── LifeCycleFixture.java │ │ ├── examples │ │ ├── decisiontables │ │ │ ├── CalculatorDecisionTableFixture.java │ │ │ ├── ExtendedLifeCycleFixture.java │ │ │ ├── LifeCycleFixture.java │ │ │ ├── LifeCycleFixtureParallel.java │ │ │ └── MalformedFixtures.java │ │ └── scenarios │ │ │ ├── ExtendedLifeCycleFixture.java │ │ │ ├── LifeCycleFixture.java │ │ │ ├── MalformedFixtures.java │ │ │ └── SelfCheckoutScenarioFixture.java │ │ └── groups │ │ └── LifeCycleFixture.java │ └── kotlin │ └── org │ └── livingdoc │ └── engine │ ├── LivingDocTest.kt │ ├── MockkExt.kt │ ├── algo │ └── StemmerTest.kt │ ├── execution │ ├── documents │ │ └── DocumentExecutionTest.kt │ ├── examples │ │ ├── decisiontables │ │ │ ├── DecisionTableFixtureCheckerTest.kt │ │ │ ├── DecisionTableFixtureWrapperTest.kt │ │ │ └── DecisionTableNoFixtureTest.kt │ │ └── scenarios │ │ │ ├── ScenarioExecutionTest.kt │ │ │ ├── ScenarioFixtureCheckerTest.kt │ │ │ ├── ScenarioNoFixtureExecutionTest.kt │ │ │ └── matching │ │ │ ├── MatchingFunctinsTest.kt │ │ │ ├── RegMatchingTest.kt │ │ │ ├── ScenarioStepMatcherTest.kt │ │ │ ├── StemmerHandlerTest.kt │ │ │ └── StepTemplateTest.kt │ └── groups │ │ └── GroupExecutionTest.kt │ ├── fixtures │ ├── FixtureFieldInjectorTest.kt │ └── FixtureMethodInvokerTest.kt │ └── resources │ ├── DeclaringGroup.kt │ ├── DisabledDecisionTableDocument.kt │ ├── DisabledExecutableDocument.kt │ └── DisabledScenarioDocument.kt ├── livingdoc-extensions-api ├── livingdoc-extensions-api.gradle.kts └── src │ └── main │ └── kotlin │ └── org │ └── livingdoc │ ├── extension │ └── Extension.kt │ ├── reports │ └── spi │ │ ├── Format.kt │ │ └── ReportRenderer.kt │ ├── repositories │ ├── Document.kt │ ├── DocumentFormat.kt │ ├── DocumentNotFoundException.kt │ ├── DocumentRepository.kt │ ├── DocumentRepositoryFactory.kt │ └── model │ │ ├── TestData.kt │ │ └── TestDataDescription.kt │ └── results │ ├── Status.kt │ ├── TestDataResult.kt │ └── documents │ └── DocumentResult.kt ├── livingdoc-format-gherkin ├── livingdoc-format-gherkin.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── livingdoc │ │ │ └── repositories │ │ │ └── format │ │ │ ├── DataTable.kt │ │ │ └── GherkinFormat.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.livingdoc.repositories.DocumentFormat │ └── test │ └── kotlin │ └── org │ └── livingdoc │ └── repositories │ └── format │ ├── GherkinFormatTest.kt │ └── GherkinFormatTestData.kt ├── livingdoc-junit-engine ├── livingdoc-junit-engine.gradle.kts └── src │ └── main │ ├── kotlin │ └── org │ │ └── livingdoc │ │ └── junit │ │ └── engine │ │ ├── LivingDocContext.kt │ │ ├── LivingDocTestEngine.kt │ │ ├── descriptors │ │ ├── DecisionTableTestDescriptor.kt │ │ ├── EngineDescriptor.kt │ │ ├── ExecutableDocumentDescriptor.kt │ │ └── ScenarioTestDescriptor.kt │ │ └── discovery │ │ ├── ClassSelectorHandler.kt │ │ ├── ClasspathRootSelectorHandler.kt │ │ ├── PackageSelectorHandler.kt │ │ └── SelectorHandler.kt │ └── resources │ └── META-INF │ └── services │ └── org.junit.platform.engine.TestEngine ├── livingdoc-reports ├── livingdoc-reports.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── livingdoc │ │ │ └── reports │ │ │ ├── ReportWriter.kt │ │ │ ├── ReportsManager.kt │ │ │ ├── config │ │ │ ├── ReportDefinition.kt │ │ │ └── ReportsConfig.kt │ │ │ ├── confluence │ │ │ ├── attachment │ │ │ │ ├── ConfluenceAttachmentReportConfig.kt │ │ │ │ └── ConfluenceAttachmentReportRenderer.kt │ │ │ └── tree │ │ │ │ ├── ConfluencePageTreeReportConfig.kt │ │ │ │ ├── ConfluencePageTreeReportRenderer.kt │ │ │ │ └── elements │ │ │ │ ├── ConfluenceError.kt │ │ │ │ ├── ConfluenceIndex.kt │ │ │ │ ├── ConfluenceLink.kt │ │ │ │ ├── ConfluencePage.kt │ │ │ │ ├── ConfluenceReport.kt │ │ │ │ ├── ConfluenceStatus.kt │ │ │ │ ├── ConfluenceStatusBar.kt │ │ │ │ ├── HtmlList.kt │ │ │ │ ├── HtmlTable.kt │ │ │ │ └── HtmlUtil.kt │ │ │ ├── html │ │ │ ├── HtmlReportConfig.kt │ │ │ ├── HtmlReportRenderer.kt │ │ │ ├── HtmlReportTemplate.kt │ │ │ ├── ReportScript.kt │ │ │ ├── ReportStyle.kt │ │ │ └── elements │ │ │ │ ├── HtmlColumnLayout.kt │ │ │ │ ├── HtmlDescription.kt │ │ │ │ ├── HtmlElement.kt │ │ │ │ ├── HtmlErrors.kt │ │ │ │ ├── HtmlFooter.kt │ │ │ │ ├── HtmlLink.kt │ │ │ │ ├── HtmlList.kt │ │ │ │ ├── HtmlTable.kt │ │ │ │ ├── HtmlTitle.kt │ │ │ │ └── HtmlUtil.kt │ │ │ └── json │ │ │ ├── JsonReportConfig.kt │ │ │ └── JsonReportRenderer.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.livingdoc.reports.spi.ReportRenderer │ └── test │ └── kotlin │ └── org │ └── livingdoc │ └── reports │ ├── ReportWriterTest.kt │ ├── ReportsManagerTest.kt │ ├── confluence │ ├── attachment │ │ └── ConfluenceAttachmentReportRendererTest.kt │ └── tree │ │ ├── ConfluencePageTreeReportConfigTest.kt │ │ └── elements │ │ ├── ConfluenceIndexTest.kt │ │ ├── ConfluenceLinkTest.kt │ │ └── ConfluenceReportTest.kt │ ├── html │ └── HtmlReportRendererTest.kt │ └── json │ └── JsonReportRendererTest.kt ├── livingdoc-repositories ├── livingdoc-repositories.gradle.kts └── src │ ├── main │ ├── kotlin │ │ └── org │ │ │ └── livingdoc │ │ │ └── repositories │ │ │ ├── ParseException.kt │ │ │ ├── RepositoryManager.kt │ │ │ ├── cache │ │ │ ├── CacheConfiguration.kt │ │ │ ├── CacheHelper.kt │ │ │ └── InvalidCachePolicyException.kt │ │ │ ├── config │ │ │ ├── RepositoryConfiguration.kt │ │ │ └── RepositoryDefinition.kt │ │ │ └── format │ │ │ ├── DocumentFormatManager.kt │ │ │ ├── DocumentFormatNotFoundException.kt │ │ │ ├── HtmlDocument.kt │ │ │ ├── HtmlFormat.kt │ │ │ ├── MarkdownFormat.kt │ │ │ └── ParseContext.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ └── org.livingdoc.repositories.DocumentFormat │ └── test │ ├── kotlin │ └── org │ │ └── livingdoc │ │ └── repositories │ │ ├── RepositoryManagerTest.kt │ │ ├── cache │ │ └── CacheHelperTest.kt │ │ └── format │ │ ├── DemoDocumentFormat.kt │ │ ├── DocumentFormatManagerTest.kt │ │ ├── HtmlFormatTest.kt │ │ ├── HtmlFormatTestData.kt │ │ ├── HtmlGherkinFormatTest.kt │ │ ├── HtmlGherkinFormatTestData.kt │ │ └── MarkdownFormatTest.kt │ └── resources │ └── META-INF │ └── services │ └── org.livingdoc.repositories.DocumentFormat ├── livingdoc-repository-confluence ├── livingdoc-repository-confluence.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── repositories │ │ └── confluence │ │ ├── ConfluenceDocumentNotFoundException.kt │ │ ├── ConfluenceRepository.kt │ │ ├── ConfluenceRepositoryConfig.kt │ │ └── ConfluenceRepositoryFactory.kt │ └── test │ └── kotlin │ └── org │ └── livingdoc │ └── repositories │ └── confluence │ ├── ConfluenceRepositoryFactoryTest.kt │ └── ConfluenceRepositoryTest.kt ├── livingdoc-repository-file ├── livingdoc-repository-file.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── repositories │ │ └── file │ │ ├── DocumentFile.kt │ │ ├── FileDocumentNotFoundException.kt │ │ ├── FileRepository.kt │ │ ├── FileRepositoryConfig.kt │ │ ├── FileRepositoryFactory.kt │ │ └── FileResolver.kt │ └── test │ ├── docs │ └── FileRepositoryIntegrationTest.md │ ├── kotlin │ └── org │ │ └── livingdoc │ │ └── repositories │ │ └── file │ │ ├── FileRepositoryIntegrationTest.kt │ │ └── FileRepositoryTest.kt │ └── resources │ ├── FileRepositoryIntegrationTestMarkdown.md │ └── livingdoc.yml ├── livingdoc-repository-git ├── livingdoc-repository-git.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── repositories │ │ └── git │ │ ├── GitDocumentIdentifier.kt │ │ ├── GitFileResolver.kt │ │ ├── GitRepository.kt │ │ ├── GitRepositoryConfig.kt │ │ ├── GitRepositoryFactory.kt │ │ └── GitRepositoryResolver.kt │ └── test │ └── kotlin │ └── org │ └── livingdoc │ └── repositories │ └── git │ ├── GitDocumentIdentifierTest.kt │ ├── GitFileResolverTest.kt │ ├── GitRepositoryFactoryTest.kt │ ├── GitRepositoryResolverTest.kt │ └── GitRepositoryTest.kt ├── livingdoc-repository-rest ├── livingdoc-repository-rest.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── repositories │ │ └── rest │ │ ├── RESTDocumentNotFoundException.kt │ │ ├── RESTRepository.kt │ │ ├── RESTRepositoryConfig.kt │ │ └── RESTRepositoryFactory.kt │ └── test │ ├── docs │ └── RestRepositoryIntegrationTest.md │ ├── kotlin │ └── org │ │ └── livingdoc │ │ └── repositories │ │ └── rest │ │ ├── RESTRepositoryTest.kt │ │ ├── RESTRepositoryUnitTest.kt │ │ └── RestRepositoryIntegrationTest.kt │ └── resources │ ├── __files │ └── Testing.html │ └── livingdoc.yml ├── livingdoc-results ├── livingdoc-results.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── results │ │ └── examples │ │ ├── decisiontables │ │ ├── DecisionTableResult.kt │ │ ├── FieldResult.kt │ │ └── RowResult.kt │ │ └── scenarios │ │ ├── ScenarioResult.kt │ │ └── StepResult.kt │ └── test │ └── kotlin │ └── org │ └── livingdoc │ └── results │ └── examples │ ├── decisiontables │ ├── DecisionTableResultBuilderTest.kt │ ├── FieldResultBuilderTest.kt │ └── RowResultBuilderTest.kt │ └── scenarios │ ├── ScenarioResultBuilderTest.kt │ └── StepResultBuilderTest.kt ├── livingdoc-testdata ├── livingdoc-testdata.gradle.kts └── src │ └── main │ └── kotlin │ └── org │ └── livingdoc │ └── repositories │ └── model │ ├── decisiontable │ ├── DecisionTable.kt │ ├── Field.kt │ ├── Header.kt │ └── Row.kt │ └── scenario │ ├── Scenario.kt │ └── Step.kt ├── livingdoc-tests ├── livingdoc-tests.gradle.kts └── src │ ├── main │ └── kotlin │ │ └── org │ │ └── livingdoc │ │ └── example │ │ ├── Calculator.kt │ │ ├── CalculatorInt.kt │ │ ├── CoffeeMachine.kt │ │ ├── CustomType.kt │ │ ├── Divider.kt │ │ └── TextFunctions.kt │ └── test │ ├── docs │ ├── Calculator.feature │ ├── Calculator.html │ ├── Calculator.md │ ├── CalculatorEmptyCell.html │ ├── CalculatorEmptyCell.md │ ├── CalculatorGherkin.html │ ├── CalculatorInt.md │ ├── CalculatorManual.html │ ├── CalculatorManual.md │ ├── ConfluenceCheckbox.html │ ├── DividerFailFast.md │ ├── GermanCalculator.feature │ ├── MultilineArgumentCalculator.feature │ ├── OutlineCalculator.feature │ ├── TestTexts.md │ └── TypeConverter.md │ ├── kotlin │ └── org │ │ └── livingdoc │ │ └── example │ │ ├── CalculatorDocumentDisabledExecutableDocument.kt │ │ ├── CalculatorDocumentDisabledFixtures.kt │ │ ├── CalculatorDocumentHtml.kt │ │ ├── CalculatorDocumentHtmlEmptyCell.kt │ │ ├── CalculatorDocumentHtmlManual.kt │ │ ├── CalculatorDocumentMd.kt │ │ ├── CalculatorDocumentMdCacheREST.kt │ │ ├── CalculatorDocumentMdCacheRESTNoInternet.kt │ │ ├── CalculatorDocumentMdEmptyCell.kt │ │ ├── CalculatorDocumentMdManual.kt │ │ ├── CalculatorGherkin.kt │ │ ├── CalculatorGherkinHtml.kt │ │ ├── CalculatorIntDocumentMdException.kt │ │ ├── CalculatorScenarioOutlineGherkin.kt │ │ ├── ConfluenceCheckboxHtml.kt │ │ ├── DividerDocumentMdFailFast.kt │ │ ├── DividerDocumentMdParallelFailFast.kt │ │ ├── ExternalGroupedDocument.kt │ │ ├── GermanCalculatorGherkin.kt │ │ ├── GroupedDocuments.kt │ │ ├── MultilineArgumentCalculatorGherkin.kt │ │ ├── SlowCalculatorDocumentMd.kt │ │ ├── TextFunctionsBeforeAndAfterMD.kt │ │ ├── TextFunctionsMD.kt │ │ └── TypeConverterMD.kt │ └── resources │ ├── __files │ └── TestingCache.html │ └── livingdoc.yml └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.sh text eol=lf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Specification** 11 | - LivingDoc-Version: 12 | - Platform: 13 | - Subsystem: 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **To Reproduce** 22 | Steps to reproduce the behavior: 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | 28 | **Screenshots** 29 | If applicable, add screenshots to help explain your problem. 30 | 31 | **Possible Solution** 32 | Not obligatory, but suggest a fix/reason for the bug, 33 | 34 | **Context (Environment)** 35 | How has this issue affected you? What are you trying to accomplish? 36 | Providing context helps us come up with a solution that is most useful in the real world 37 | 38 | **Detailed Description** 39 | Provide a detailed description of the change or addition you are proposing 40 | 41 | **Possible Implementation** 42 | Not obligatory, but suggest an idea for implementing addition or change 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/user-story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: User Story 3 | about: A template for new user stories 4 | title: '' 5 | labels: type::user-story 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## User-Story 11 | 12 | **As a** 13 | [type of user] 14 | 15 | **, I want** 16 | [goal or objective of the user] 17 | 18 | **so that** 19 | [benefit and value the feature should achieve] 20 | . 21 | 22 | _Acceptance Criteria:_ 23 | 24 | - [ ] Criteria 1 25 | 26 | _Tasks:_ 27 | 28 | - [ ] Task 1 29 | - [ ] #LinkedIssueNumber 30 | 31 | _Description:_ 32 | Context, Implementation, ... 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | 3 | # eclipse 4 | .project 5 | .classpath 6 | .eclipse-pmd 7 | **/.settings/ 8 | **/.metadata/ 9 | 10 | ## intellij 11 | # User-specific stuff 12 | .idea/**/workspace.xml 13 | .idea/**/tasks.xml 14 | .idea/misc.xml 15 | .idea/**/dictionaries 16 | .idea/checkstyle-idea\.xml 17 | .idea/modules\.xml 18 | .idea/eclipseCodeFormatter\.xml 19 | .idea/**/shelf 20 | # Sensitive or high-churn files 21 | .idea/**/dataSources/ 22 | .idea/**/dataSources.ids 23 | .idea/**/dataSources.local.xml 24 | .idea/**/sqlDataSources.xml 25 | .idea/**/dynamic.xml 26 | .idea/**/uiDesigner.xml 27 | .idea/**/dbnavigator.xml 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | # File-based project format 32 | *.iws 33 | # IntelliJ 34 | out/ 35 | *.iml 36 | # Editor-based Rest Client 37 | .idea/httpRequests 38 | 39 | # gradle 40 | **/build/ 41 | .gradle 42 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/bug_report.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | # Bug report 6 | ## Specifications 7 | 8 | - LivingDoc-Version: 9 | - Platform: 10 | - Subsystem: 11 | 12 | ## Expected Behavior 13 | 14 | 15 | ## Current Behavior 16 | 17 | 18 | ### Screenshots 19 | 20 | 21 | ## Possible Solution 22 | 23 | 24 | ## Steps to Reproduce 25 | 26 | 27 | 1. 28 | 2. 29 | 3. 30 | 4. 31 | 32 | ## Context (Environment) 33 | 34 | 35 | 36 | ## Detailed Description 37 | 38 | 39 | ## Possible Implementation 40 | 41 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/user_story.md: -------------------------------------------------------------------------------- 1 | ## User-Story 2 | 3 | **As a** 4 | 5 | 6 | **, I want** 7 | 8 | 9 | **so that** 10 | 11 | 12 | . 13 | 14 | _Acceptance Criteria:_ 15 | 16 | 19 | 20 | _Tasks:_ 21 | 22 | 26 | 27 | 28 | _Description:_ 29 | -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/merge_request.md: -------------------------------------------------------------------------------- 1 | ## Acceptance criteria 2 | 3 | #### Code quality 4 | 5 | - [ ] Code does not contain TODOs, unused private functions or console debugging outputs (exceptions are documented in commit-messages, issue or wiki) 6 | - [ ] All classes and functions (except for getters and setters) are documented with [KDoc](https://kotlinlang.org/docs/reference/kotlin-doc.html) 7 | - [ ] Code has been inspected and deemed to have decent quality 8 | - [ ] If this is a bug-fix, a test covering the bug is present 9 | - [ ] The coverage is equal or higher than before 10 | 11 | #### Documentation 12 | 13 | - [ ] If needed, Architectural Decisions are documented as described in the [Wiki](https://gilbert.informatik.uni-stuttgart.de/enpro-ws2019-20/enpro-livingdoc/wikis/Product-Documentation/System-Documentation/Architecture-and-Design/ADR) 14 | - [ ] If needed, changes are documented in the [CHANGELOG](https://gilbert.informatik.uni-stuttgart.de/enpro-ws2019-20/enpro-livingdoc/blob/master/CHANGELOG.adoc) 15 | - [ ] If needed, changes are documented in the [Wiki](https://gilbert.informatik.uni-stuttgart.de/enpro-ws2019-20/enpro-livingdoc/wikis/home) 16 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinScripting.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | "org.jetbrains.dokka" 4 | kotlin("jvm") version embeddedKotlinVersion 5 | } 6 | 7 | repositories { 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | implementation(kotlin("gradle-plugin")) 13 | } 14 | -------------------------------------------------------------------------------- /buildSrc/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Versions.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.JavaVersion 2 | 3 | object Versions { 4 | 5 | //Language 6 | val jvmTarget = JavaVersion.VERSION_1_8 7 | val kotlinVersion = "1.3.50" 8 | val kotlinApi = "1.3" 9 | 10 | //Dependencies 11 | val slf4j = "1.7.25" 12 | val snakeyaml= "1.18" 13 | val jsoup = "1.11.2" 14 | val flexmark = "0.28.32" 15 | val klaxon = "5.2" 16 | val gherkin = "9.0.0" 17 | const val jgit = "5.6.1.202002131546-r" 18 | 19 | //Test 20 | val junitPlatform = "1.5.2" 21 | val junitJupiter = "5.5.2" 22 | val mockk = "1.9.3" 23 | val logback = "1.2.3" 24 | val assertJ = "3.11.1" 25 | val wiremock = "2.25.1" 26 | 27 | //Plugins 28 | val buildScanPlugin = "2.2.1" 29 | val spotlessPlugin = "3.23.0" 30 | val dokkaPlugin = "0.10.0" 31 | 32 | //AsciiDoctor 33 | val asciidoctorPlugin = "1.5.3" 34 | 35 | 36 | //Tools 37 | val detektVersion = "1.0.1" 38 | val ktlint = "0.33.0" 39 | } 40 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/java-project-config.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | `maven-publish` 4 | kotlin("jvm") 5 | } 6 | 7 | val sourcesJar by tasks.creating(Jar::class) { 8 | dependsOn(tasks.classes) 9 | archiveClassifier.set("sources") 10 | from(sourceSets.main.get().allSource) 11 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 12 | } 13 | 14 | val javadocJar by tasks.creating(Jar::class) { 15 | archiveClassifier.set("javadoc") 16 | from(tasks.javadoc) 17 | } 18 | 19 | tasks.withType().configureEach { 20 | options.encoding = "UTF-8" 21 | } 22 | 23 | publishing { 24 | publications { 25 | create("maven") { 26 | artifact(sourcesJar) 27 | artifact(javadocJar) 28 | from(components["java"]) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/kotlin-project-config.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | id("java-project-config") 5 | kotlin("jvm") 6 | } 7 | 8 | tasks.withType().configureEach{ 9 | kotlinOptions { 10 | jvmTarget = Versions.jvmTarget.toString() 11 | apiVersion = Versions.kotlinApi 12 | languageVersion = Versions.kotlinApi 13 | } 14 | } 15 | 16 | tasks.withType { 17 | useJUnitPlatform() 18 | systemProperties(System.getProperties().filter { (it.key as? String)?.startsWith("livingdoc")?: false } as Map) 19 | } 20 | 21 | dependencies { 22 | implementation(kotlin("stdlib-jdk8")) 23 | testImplementation ("org.junit.jupiter:junit-jupiter-api:${Versions.junitJupiter}") 24 | testImplementation ("org.junit.jupiter:junit-jupiter-params:${Versions.junitJupiter}") 25 | testImplementation("io.mockk:mockk:${Versions.mockk}") 26 | 27 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Versions.junitJupiter}") 28 | } 29 | -------------------------------------------------------------------------------- /detekt-config.yml: -------------------------------------------------------------------------------- 1 | build: 2 | maxIssues: 0 3 | 4 | complexity: 5 | TooManyFunctions: 6 | ignorePrivate: true 7 | 8 | exceptions: 9 | TooGenericExceptionCaught: 10 | active: true 11 | exceptionNames: 12 | - ArrayIndexOutOfBoundsException 13 | - Error 14 | - IllegalMonitorStateException 15 | - NullPointerException 16 | - IndexOutOfBoundsException 17 | - Throwable 18 | 19 | style: 20 | ForbiddenComment: 21 | active: false 22 | ThrowsCount: 23 | active: false 24 | 25 | -------------------------------------------------------------------------------- /doc/README.adoc: -------------------------------------------------------------------------------- 1 | = LivingDoc 2 Developer Documentation 2 | 3 | This folder contains LD2 documentation relevant for developers. 4 | -------------------------------------------------------------------------------- /doc/decisions/README.adoc: -------------------------------------------------------------------------------- 1 | = LivingDoc2 - Architecture Decision Record 2 | 3 | Here you have an overview of all Architecture Decision Records (ADR) created so far. 4 | 5 | * link:adr-001-keep-architecture-decision-records.adoc[ADR001 - Keep ADR's] 6 | * link:adr-002-use-asciidoc-markup.adoc[ADR002 - Use AsciiDoc as markup] 7 | * link:adr-003-use-gradle-as-build-tool.adoc[ADR003 - Use Gradle as build tool] 8 | * link:adr-004-implement-junit5-engine.adoc[ADR004 - Implement JUnit 5 Engine] 9 | * link:adr-005-use-java-spi.adoc[ADR005 - Use Java SPI] 10 | -------------------------------------------------------------------------------- /doc/decisions/adr-002-use-asciidoc-markup.adoc: -------------------------------------------------------------------------------- 1 | = ADR 2: Use AsciiDoc Markup For Documentation 2 | 3 | == Context 4 | 5 | We want to create developer and end-user documentation in a textual format that 6 | we can manage alongside with code in git. Our experience suggests that it is 7 | easier to keep documentation in sync when we can keep it in version control 8 | with the code and edit it with the same tools. 9 | 10 | Markdown is a simple markup language that is widely supported and optimized for 11 | readability in text form without having to use a renderer. However, it lacks 12 | some features such as _includes_ (e.g. for code fragments) and _tables_. 13 | 14 | AsciiDoc is also widely adopted, but sacrifices some readability to support 15 | richer markup which includes the features we are missing in Markdown. 16 | 17 | 18 | == Decision 19 | 20 | We will use AsciiDoc as markup language for documentation. 21 | 22 | AsciiDoc text files should be properly formatted, so that they can be read 23 | without a renderer. They should wrap lines using hard line breaks after about 24 | 70 characters. 25 | 26 | We will render end-user documentation as HTML. 27 | 28 | 29 | == Status 30 | 31 | Accepted. 32 | 33 | 34 | == Consequences 35 | 36 | * We have support for _includes_ and _tables_. 37 | * Documentation is readable, with and without a renderer. 38 | * It is easier to update documentation alongside with the code. 39 | -------------------------------------------------------------------------------- /doc/decisions/adr-003-use-gradle-as-build-tool.adoc: -------------------------------------------------------------------------------- 1 | = ADR 3: Use Gradle as Build Tool 2 | 3 | == Context 4 | 5 | In order to build the different components of LivingDoc 2 we need a build tool. 6 | We considered the two major Java build tools: 7 | 8 | - Maven 9 | - Gradle 10 | 11 | Maven is good for straight-forward and simple projects like libraries or 12 | microservices, because it offers a lot of structure and convention that can 13 | initially boost development speed. Experience has taught us that Maven reaches 14 | its limit with more complex projects, which require a customized build. 15 | Customization in Maven means plugin development. 16 | 17 | Gradle on the other hand is an internal DSL, which gives us the option to fall 18 | back on Groovy to _program_ a build. 19 | 20 | LivingDoc is a complex project and we expect to profit from build customization 21 | fairly early during LD2 development. 22 | 23 | 24 | == Decision 25 | 26 | We will use Gradle from the outset. 27 | 28 | 29 | == Status 30 | 31 | Accepted. 32 | 33 | 34 | == Consequences 35 | 36 | * We can gradually move from a simple initial build to a more involved build 37 | process. 38 | * Developers have to be familiar with Gradle and, for customizing the build, 39 | with Groovy. 40 | -------------------------------------------------------------------------------- /doc/decisions/adr-004-implement-junit5-engine.adoc: -------------------------------------------------------------------------------- 1 | = ADR 4: Implement JUnit 5 Test Engine 2 | 3 | == Context 4 | 5 | In LivingDoc Legacy we had multiple plugins which provided execution of documents 6 | for different contexts: 7 | 8 | - Eclipse 9 | - IntelliJ 10 | - Maven 11 | - Gradle 12 | 13 | Each of which needed to be maintained. Having just one way of executing documents, 14 | which works for all of these contexts, would save us a lot of maintenance work. 15 | 16 | With the development of JUnit 5 there is a way of implementing generic _test engines_ 17 | which are used by the _JUnit Platform_ to execute all kinds of custom tests. The 18 | platform is integrated in all of the popular IDEs and build tools. 19 | 20 | We will implement a JUnit 5 _test engine_ for the execution of LivingDoc documents. 21 | This test engine will be the bridge between the _JUnit Platform_ and LivingDoc. 22 | 23 | 24 | == Decision 25 | 26 | We will implement a JUnit 5 test engine. 27 | 28 | 29 | == Status 30 | 31 | Accepted. 32 | 33 | 34 | == Consequences 35 | 36 | * We'll have to maintain only one integration module. 37 | * We don't need to handle the integration into IDEs or build tools 38 | * We won't provide UIs for the IDEs like we did in LivingDoc Legacy 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LivingDoc/livingdoc/f3d52b8bacbdf81905e4b4a753d75f584329b297/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /livingdoc-api/livingdoc-api.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-project-config` 3 | } 4 | 5 | dependencies { 6 | compileOnly("org.junit.platform:junit-platform-commons:${Versions.junitPlatform}") 7 | } 8 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/After.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api; 2 | 3 | import org.livingdoc.api.documents.ExecutableDocument; 4 | import org.livingdoc.api.fixtures.scenarios.ScenarioFixture; 5 | import org.livingdoc.api.fixtures.scenarios.Step; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | 13 | /** 14 | * Methods annotated with this annotation are invoked after the last fixture of an {@link ExecutableDocument} or the 15 | * last scenario {@link Step} method was invoked. 16 | *

17 | * Constraints: 18 | *

    19 | *
  1. The annotated method must not have any parameters!
  2. 20 | *
  3. If multiple methods of a single fixture are annotated the invocation order is non-deterministic!
  4. 21 | *
22 | * 23 | * @see ExecutableDocument 24 | * @see Step 25 | * @see ScenarioFixture 26 | * @since 2.0 27 | */ 28 | @Target(ElementType.METHOD) 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface After { 31 | } 32 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/Before.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api; 2 | 3 | import org.livingdoc.api.documents.ExecutableDocument; 4 | import org.livingdoc.api.fixtures.scenarios.ScenarioFixture; 5 | import org.livingdoc.api.fixtures.scenarios.Step; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | 13 | /** 14 | * Methods annotated with this annotation are invoked before the first fixture of an {@link ExecutableDocument} or 15 | * before the first scenario {@link Step} method is invoked. 16 | *

17 | * Constraints: 18 | *

    19 | *
  1. The annotated method must not have any parameters!
  2. 20 | *
  3. If multiple methods of a single fixture are annotated the invocation order is non-deterministic!
  4. 21 | *
22 | * 23 | * @see ExecutableDocument 24 | * @see Step 25 | * @see ScenarioFixture 26 | * @since 2.0 27 | */ 28 | @Target(ElementType.METHOD) 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface Before { 31 | } 32 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/conversion/ConversionException.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.conversion; 2 | 3 | /** 4 | * This exception type (or one of its sub-classes) is thrown in cases where a {@link TypeConverter} could not convert a 5 | * value given to it. Since this generally means the value was 'wrong', this exception should be treated as a terminal 6 | * condition for the current test case. 7 | * 8 | * @since 2.0 9 | */ 10 | public class ConversionException extends RuntimeException { 11 | /** 12 | * This Constructor takes the provided message and handles it like the RuntimeException 13 | * @param message the exception message 14 | */ 15 | public ConversionException(String message) { 16 | super(message); 17 | } 18 | 19 | /** 20 | * This Constructor takes the provided message cause and handles it like the RuntimeException 21 | * @param message the exception message 22 | * @param cause the cause 23 | */ 24 | public ConversionException(String message, Throwable cause) { 25 | super(message, cause); 26 | } 27 | 28 | /** 29 | * This Constructor takes the provided cause and handles it like the RuntimeException 30 | * @param cause the cause 31 | */ 32 | public ConversionException(Throwable cause) { 33 | super(cause); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/conversion/Format.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.conversion; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** 10 | * Defines a format to be interpreted and used by a {@link TypeConverter}. The syntax of that format depends on the {@link 11 | * TypeConverter}. Generally these formats tend to be regular expressions or date formats. For details you'll have to take a 12 | * look at the used {@link TypeConverter}'s documentation. 13 | * 14 | * @since 2.0 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ ElementType.FIELD, ElementType.PARAMETER }) 18 | public @interface Format { 19 | String value(); 20 | } 21 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/conversion/Language.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.conversion; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** 10 | * Overrides the used language ({@link java.util.Locale}) for the annotated field or parameter. 11 | * A full list of {@link TypeConverter} considering this annotation can be found in the documentation. 12 | * The {@link #value()} has to be specified according to the BCP 47 13 | * standard. 14 | *

15 | * Examples for this are: {@code de}, {@code de-DE}, {@code en-US} etc. 16 | * 17 | * @see java.util.Locale#forLanguageTag(String) 18 | * @since 2.0 19 | */ 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Target({ ElementType.FIELD, ElementType.PARAMETER }) 22 | public @interface Language { 23 | String value(); 24 | } 25 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/disabled/Disabled.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.disabled; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Disabled is used to signal that the annotated test class is currently disabled and should not be 10 | * executed. 11 | */ 12 | @Target(ElementType.TYPE) 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface Disabled { 15 | 16 | String value() default ""; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/documents/FailFast.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.documents; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | /** 7 | * This annotation is used to mark a document to use fail fast behaviour. Whenever a test fails, the whole execution 8 | * should stop. 9 | */ 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface FailFast { 13 | /** 14 | * The Array of Exceptions that should lead to a fast fail 15 | */ 16 | Class[] onExceptionTypes() default Throwable.class; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/documents/Group.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.documents; 2 | 3 | import org.junit.platform.commons.annotation.Testable; 4 | import org.livingdoc.api.After; 5 | import org.livingdoc.api.Before; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * A Group contains a number of {@link ExecutableDocument ExecutableDocuments}, which are executed together. 14 | * 15 | * The execution of different Groups is guaranteed to happen in sequence. 16 | * 17 | * A Group can also define {@link Before} and {@link After} hooks, which are run before or after all documents. 18 | * 19 | * @see ExecutableDocument 20 | * @see Before 21 | * @see After 22 | */ 23 | @Testable 24 | @Target(ElementType.TYPE) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface Group { 27 | } 28 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/exception/ExampleSyntax.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.exception; 2 | 3 | /** 4 | * The syntax used by the business experts in the examples. 5 | */ 6 | public class ExampleSyntax { 7 | /** 8 | * The string that the business expert defines in the Example to indicate 9 | * an exception is expected to be thrown. 10 | */ 11 | public static final String EXCEPTION = "error"; 12 | } 13 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/fixtures/decisiontables/AfterRow.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.fixtures.decisiontables; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** 10 | * Methods annotated with this annotation are invoked once for each row of the decision table example just after it was 11 | * evaluated. These methods are intended to close resources initialized in {@link BeforeRow} annotated methods. 12 | *

13 | * Constraints: 14 | *

    15 | *
  1. The annotated method must not have any parameters!
  2. 16 | *
  3. If multiple methods of a single fixture are annotated the invocation order is non-deterministic!
  4. 17 | *
18 | * 19 | * @see DecisionTableFixture 20 | * @since 2.0 21 | */ 22 | @Target(ElementType.METHOD) 23 | @Retention(RetentionPolicy.RUNTIME) 24 | public @interface AfterRow { 25 | } 26 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/fixtures/decisiontables/BeforeFirstCheck.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.fixtures.decisiontables; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** 10 | * Methods annotated with this annotation are always invoked before the first {@link Check} of each row is executed. 11 | * These methods are generally used to execute test code based on the {@link Input} values and store the results to be 12 | * evaluated by the {@link Check} methods. This is a purely optional mechanic! Depending on the test case, directly 13 | * executing that logic inside a {@link Check} method might be the best approach. 14 | *

15 | * Constraints: 16 | *

    17 | *
  1. The annotated method must not have any parameters!
  2. 18 | *
  3. If multiple methods of a single fixture are annotated the invocation order is non-deterministic!
  4. 19 | *
20 | * 21 | * @see DecisionTableFixture 22 | * @see Check 23 | * @since 2.0 24 | */ 25 | @Target(ElementType.METHOD) 26 | @Retention(RetentionPolicy.RUNTIME) 27 | public @interface BeforeFirstCheck { 28 | } 29 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/fixtures/decisiontables/BeforeRow.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.fixtures.decisiontables; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** 10 | * Methods annotated with this annotation are invoked once for each row of the decision table example just before it is 11 | * evaluated. These methods are intended to setup resources needed for the execution of a single row. 12 | *

13 | * Constraints: 14 | *

    15 | *
  1. The annotated method must not have any parameters!
  2. 16 | *
  3. If multiple methods of a single fixture are annotated the invocation order is non-deterministic!
  4. 17 | *
18 | * 19 | * @see DecisionTableFixture 20 | * @since 2.0 21 | */ 22 | @Target(ElementType.METHOD) 23 | @Retention(RetentionPolicy.RUNTIME) 24 | public @interface BeforeRow { 25 | } 26 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/fixtures/scenarios/Binding.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.fixtures.scenarios; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | 9 | /** 10 | * Binds a name to the annotated parameter. If the bound name occurs as parameter in a {@link Step} 11 | * annotation of this method, the extracted value will be converted and passed to this method during 12 | * execution. 13 | * 14 | * @see Step 15 | * @see ScenarioFixture 16 | * @since 2.0 17 | */ 18 | @Target(ElementType.PARAMETER) 19 | @Retention(RetentionPolicy.RUNTIME) 20 | public @interface Binding { 21 | String value(); 22 | } 23 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/fixtures/scenarios/ScenarioFixture.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.fixtures.scenarios; 2 | 3 | import org.livingdoc.api.After; 4 | import org.livingdoc.api.Before; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Repeatable; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | 13 | /** 14 | * Annotating a class with this annotation declares it to be a fixture for a scenario example. 15 | *

16 | * Scenario: An example describing a single test case as a sequence of steps. Each step is represented as a 17 | * sentence containing either one or more test inputs or one or more expectations. 18 | *

19 | * LivingDoc will evaluate the fixture class for consistency when it is first loaded. Only scenario related annotations can 20 | * be used within a scenario fixture! 21 | * 22 | * @see Before 23 | * @see After 24 | * @see Step 25 | * @since 2.0 26 | */ 27 | @Repeatable(ScenarioFixture.ScenarioFixtures.class) 28 | @Target(ElementType.TYPE) 29 | @Retention(RetentionPolicy.RUNTIME) 30 | public @interface ScenarioFixture { 31 | 32 | String[] value(); 33 | 34 | @Target(ElementType.TYPE) 35 | @Retention(RetentionPolicy.RUNTIME) 36 | @interface ScenarioFixtures { 37 | ScenarioFixture[] value(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/tagging/Tag.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.tagging; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | /** 7 | * This annotation is used to specify a tag for a test, e.g. to categorize tests by environment, topic or other things. 8 | */ 9 | @Target(ElementType.TYPE) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Repeatable(Tags.class) 12 | public @interface Tag { 13 | /** 14 | * The tag as a String 15 | */ 16 | String value(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /livingdoc-api/src/main/java/org/livingdoc/api/tagging/Tags.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.api.tagging; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | /** 7 | * This annotation is used to specify an array of tags for a test, e.g. to categorize tests by environment, topic or 8 | * other things. 9 | */ 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface Tags { 13 | /** 14 | * The tag as a String 15 | */ 16 | Tag[] value(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /livingdoc-config/livingdoc-config.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | dependencies { 6 | implementation("org.yaml:snakeyaml:${Versions.snakeyaml}") 7 | implementation(kotlin("reflect", Versions.kotlinVersion)) 8 | 9 | testCompile("org.assertj:assertj-core:${Versions.assertJ}") 10 | } 11 | -------------------------------------------------------------------------------- /livingdoc-config/src/test/resources/test-config.yaml: -------------------------------------------------------------------------------- 1 | list: 2 | - name: bla 3 | value: 1 4 | - name: foo 5 | value: 0 6 | object: 7 | a: 8 | b: 9 | - "string" 10 | - 11 | fail: 12 | test1: 13 | test2: -------------------------------------------------------------------------------- /livingdoc-converters/livingdoc-converters.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | dependencies { 6 | implementation("org.slf4j:slf4j-api:${Versions.slf4j}") 7 | implementation(kotlin("reflect", Versions.kotlinVersion)) 8 | implementation(project(":livingdoc-api")) 9 | implementation("com.beust:klaxon:${Versions.klaxon}") 10 | 11 | testCompile("ch.qos.logback:logback-classic:${Versions.logback}") 12 | testCompile("org.assertj:assertj-core:${Versions.assertJ}") 13 | } 14 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/CharacterConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters 2 | 3 | import java.lang.reflect.AnnotatedElement 4 | import org.livingdoc.api.conversion.ConversionException 5 | import org.livingdoc.api.conversion.TypeConverter 6 | 7 | /** 8 | * This converter is used to convert a String with a length of 1 to a char 9 | */ 10 | open class CharacterConverter : TypeConverter { 11 | 12 | /** 13 | * This function converts the given parameter value with a length of 1 to a char. If the length of value is not 1, 14 | * this function throws a Conversion exception. 15 | * 16 | * @param value the String that should have a length of 1 17 | * @return the char value of the given String 18 | * @throws ConversionException if the length is not 1 19 | */ 20 | override fun convert(value: String, element: AnnotatedElement?, documentClass: Class<*>?): Char { 21 | if (value.length != 1) { 22 | throw ConversionException("not a char value: '$value'") 23 | } 24 | return value[0] 25 | } 26 | 27 | override fun canConvertTo(targetType: Class<*>): Boolean { 28 | val isJavaObjectType = Char::class.javaObjectType == targetType 29 | val isKotlinType = Char::class.java == targetType 30 | return isJavaObjectType || isKotlinType 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/StringConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters 2 | 3 | import java.lang.reflect.AnnotatedElement 4 | import org.livingdoc.api.conversion.TypeConverter 5 | 6 | /** 7 | * This converter is used to convert a String to a String 8 | */ 9 | open class StringConverter : TypeConverter { 10 | 11 | /** 12 | * This method 'converts' the given value to a String by returning the input string. 13 | * @param value: the input String 14 | * @return the result of the conversion as a String 15 | */ 16 | override fun convert(value: String, element: AnnotatedElement?, documentClass: Class<*>?): String = value 17 | 18 | /** 19 | * This method returns true, if the parameter targetType is the java String class. 20 | */ 21 | override fun canConvertTo(targetType: Class<*>) = String::class.java == targetType 22 | } 23 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/collection/AbstractCollectionConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection 2 | 3 | import java.lang.reflect.AnnotatedElement 4 | import org.livingdoc.api.conversion.ConversionException 5 | import org.livingdoc.api.conversion.TypeConverter 6 | import org.livingdoc.converters.TypeConverters.findTypeConverterForGenericElement 7 | import org.livingdoc.converters.collection.Tokenizer.tokenizeToStringList 8 | 9 | abstract class AbstractCollectionConverter> : TypeConverter { 10 | 11 | private val firstParameter = 0 12 | 13 | @Throws(ConversionException::class) 14 | override fun convert(value: String, element: AnnotatedElement, documentClass: Class<*>?): T { 15 | val converter = findTypeConverterForGenericElement(element, firstParameter, documentClass) 16 | val convertedValues = tokenizeToStringList(value) 17 | .map { converter.convert(it, element, documentClass) } 18 | return convertToTarget(convertedValues) 19 | } 20 | 21 | abstract fun convertToTarget(collection: List): T 22 | } 23 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/collection/ListConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection 2 | 3 | open class ListConverter : AbstractCollectionConverter>() { 4 | 5 | override fun convertToTarget(collection: List): List { 6 | return collection 7 | } 8 | 9 | override fun canConvertTo(targetType: Class<*>?) = List::class.java == targetType 10 | } 11 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/collection/MapConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection 2 | 3 | import java.lang.reflect.AnnotatedElement 4 | import org.livingdoc.api.conversion.ConversionException 5 | import org.livingdoc.api.conversion.TypeConverter 6 | import org.livingdoc.converters.TypeConverters.findTypeConverterForGenericElement 7 | import org.livingdoc.converters.collection.Tokenizer.tokenizeToMap 8 | 9 | open class MapConverter : TypeConverter> { 10 | 11 | private val keyParameter: Int = 0 12 | private val valueParameter: Int = 1 13 | 14 | @Throws(ConversionException::class) 15 | override fun convert(value: String, element: AnnotatedElement, documentClass: Class<*>?): Map { 16 | val keyConverter = findTypeConverterForGenericElement(element, keyParameter, documentClass) 17 | val valueConverter = findTypeConverterForGenericElement(element, valueParameter, documentClass) 18 | val pairs = tokenizeToMap(value) 19 | return pairs.map { (key, value) -> 20 | val convertedKey = keyConverter.convert(key, element, documentClass) 21 | val convertedValue = valueConverter.convert(value, element, documentClass) 22 | convertedKey to convertedValue 23 | }.toMap() 24 | } 25 | 26 | override fun canConvertTo(targetType: Class<*>?) = Map::class.java == targetType 27 | } 28 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/collection/SetConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection 2 | 3 | open class SetConverter : AbstractCollectionConverter>() { 4 | 5 | override fun convertToTarget(collection: List) = collection.toSet() 6 | 7 | override fun canConvertTo(targetType: Class<*>?) = Set::class.java == targetType 8 | } 9 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/exceptions/ColorFormatException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.exceptions 2 | 3 | import org.livingdoc.api.conversion.ConversionException 4 | 5 | /** 6 | * This exception is used whenever the format is not formed correctly 7 | */ 8 | class ColorFormatException(value: String?) : ConversionException("The color value $value is not valid. Either the " + 9 | "value is not defined in the property file or the value has been typed wrong.") 10 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/exceptions/MalformedFormatException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.exceptions 2 | 3 | /** 4 | * This exception is used whenever the format is not formed correctly 5 | */ 6 | class MalformedFormatException( 7 | cause: Throwable 8 | ) : RuntimeException("Custom format pattern is malformed: ${cause.message}", cause) 9 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/exceptions/NumberRangeException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.exceptions 2 | 3 | import org.livingdoc.api.conversion.ConversionException 4 | 5 | /** 6 | * This Exception is used whenever the given value is not in the required range. 7 | * @param value the value that doesn't match 8 | * @param min the lower bound of the range 9 | * @param max the upper bound of the range 10 | */ 11 | class NumberRangeException( 12 | value: Number, 13 | min: Number?, 14 | max: Number? 15 | ) : ConversionException("The number '$value' is not between '${min ?: "-Infinity"}' and '${max ?: "Infinity"}'!") 16 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/exceptions/ValueFormatException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.exceptions 2 | 3 | import org.livingdoc.api.conversion.ConversionException 4 | 5 | /** 6 | * This Exception is used whenever the given value doesn't match the required format. 7 | * @param value the value that doesn't match 8 | * @param format the format that should have been matched 9 | */ 10 | class ValueFormatException( 11 | value: String, 12 | format: String, 13 | cause: Throwable 14 | ) : ConversionException("The value '$value' does not match the expected format: $format", cause) 15 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/number/BigDecimalConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import java.math.BigDecimal 4 | 5 | /** 6 | * This converter "converts" a BigDecimal to a BigDecimal 7 | */ 8 | open class BigDecimalConverter : AbstractNumberConverter() { 9 | 10 | override val lowerBound: BigDecimal? = null 11 | override val upperBound: BigDecimal? = null 12 | 13 | /** 14 | * This function returns the given BigDecimal value. 15 | * 16 | * @param number the BigDecimal containing the value that should be returned 17 | */ 18 | override fun convertToTarget(number: BigDecimal): BigDecimal = number 19 | 20 | override fun canConvertTo(targetType: Class<*>) = BigDecimal::class.java == targetType 21 | } 22 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/number/BigIntegerConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import java.math.BigDecimal 4 | import java.math.BigInteger 5 | /** 6 | * This converter converts a BigDecimal to a BigInteger 7 | */ 8 | open class BigIntegerConverter : AbstractNumberConverter() { 9 | 10 | override val lowerBound: BigInteger? = null 11 | override val upperBound: BigInteger? = null 12 | 13 | /** 14 | * This function returns the BigInteger representation of the given BigDecimal value. 15 | * 16 | * @param number the BigDecimal containing the value that should be converted 17 | */ 18 | override fun convertToTarget(number: BigDecimal): BigInteger = number.toBigInteger() 19 | 20 | override fun canConvertTo(targetType: Class<*>) = BigInteger::class.java == targetType 21 | } 22 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/number/ByteConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import java.math.BigDecimal 4 | 5 | /** 6 | * This converter converts a BigDecimal to a Byte 7 | */ 8 | open class ByteConverter : AbstractNumberConverter() { 9 | 10 | override val lowerBound: Byte = Byte.MIN_VALUE 11 | override val upperBound: Byte = Byte.MAX_VALUE 12 | 13 | /** 14 | * This function returns the Byte representation of the given BigDecimal value. 15 | * 16 | * @param number the BigDecimal containing the value that should be converted 17 | */ 18 | override fun convertToTarget(number: BigDecimal): Byte = number.toByte() 19 | 20 | override fun canConvertTo(targetType: Class<*>): Boolean { 21 | val isJavaObjectType = Byte::class.javaObjectType == targetType 22 | val isKotlinType = Byte::class.java == targetType 23 | return isJavaObjectType || isKotlinType 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/number/IntegerConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import java.math.BigDecimal 4 | 5 | /** 6 | * This converter converts a BigDecimal number to an Integer 7 | */ 8 | open class IntegerConverter : AbstractNumberConverter() { 9 | 10 | override val lowerBound: Int = Int.MIN_VALUE 11 | override val upperBound: Int = Int.MAX_VALUE 12 | 13 | /** 14 | * This function returns the value of number as an Integer 15 | */ 16 | override fun convertToTarget(number: BigDecimal): Int = number.toInt() 17 | 18 | override fun canConvertTo(targetType: Class<*>): Boolean { 19 | val isJavaObjectType = Int::class.javaObjectType == targetType 20 | val isKotlinType = Int::class.java == targetType 21 | return isJavaObjectType || isKotlinType 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/number/LongConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import java.math.BigDecimal 4 | 5 | /** 6 | * This converter converts a BigDecimal number to a Long 7 | */ 8 | open class LongConverter : AbstractNumberConverter() { 9 | 10 | override val lowerBound: Long = Long.MIN_VALUE 11 | override val upperBound: Long = Long.MAX_VALUE 12 | 13 | /** 14 | * This function returns the value of number as a Long 15 | */ 16 | override fun convertToTarget(number: BigDecimal): Long = number.toLong() 17 | 18 | override fun canConvertTo(targetType: Class<*>): Boolean { 19 | val isJavaObjectType = Long::class.javaObjectType == targetType 20 | val isKotlinType = Long::class.java == targetType 21 | return isJavaObjectType || isKotlinType 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/number/ShortConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import java.math.BigDecimal 4 | 5 | /** 6 | * This converter converts a BigDecimal number to a Short 7 | */ 8 | open class ShortConverter : AbstractNumberConverter() { 9 | 10 | override val lowerBound: Short = Short.MIN_VALUE 11 | override val upperBound: Short = Short.MAX_VALUE 12 | 13 | /** 14 | * This function returns the value of number as a Short 15 | */ 16 | override fun convertToTarget(number: BigDecimal): Short = number.toShort() 17 | 18 | override fun canConvertTo(targetType: Class<*>): Boolean { 19 | val isJavaObjectType = Short::class.javaObjectType == targetType 20 | val isKotlinType = Short::class.java == targetType 21 | return isJavaObjectType || isKotlinType 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/time/LocalDateConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import java.time.LocalDate 4 | import java.time.format.DateTimeFormatter 5 | 6 | /** 7 | * This converter parses a String to the local date format 8 | */ 9 | open class LocalDateConverter : AbstractTemporalConverter() { 10 | 11 | override fun defaultFormatter(): DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE 12 | override fun doParse(value: String, formatter: DateTimeFormatter): LocalDate = LocalDate.parse(value, formatter) 13 | 14 | override fun canConvertTo(targetType: Class<*>) = LocalDate::class.java == targetType 15 | } 16 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/time/LocalDateTimeConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import java.time.LocalDateTime 4 | import java.time.format.DateTimeFormatter 5 | 6 | /** 7 | * This converter parses a String to the local date and time format 8 | */ 9 | open class LocalDateTimeConverter : AbstractTemporalConverter() { 10 | 11 | override fun defaultFormatter(): DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME 12 | override fun doParse(value: String, formatter: DateTimeFormatter): LocalDateTime = 13 | LocalDateTime.parse(value, formatter) 14 | 15 | override fun canConvertTo(targetType: Class<*>) = LocalDateTime::class.java == targetType 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/time/LocalTimeConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import java.time.LocalTime 4 | import java.time.format.DateTimeFormatter 5 | 6 | /** 7 | * This converter parses a String to the local time format 8 | */ 9 | open class LocalTimeConverter : AbstractTemporalConverter() { 10 | 11 | override fun defaultFormatter(): DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_TIME 12 | override fun doParse(value: String, formatter: DateTimeFormatter): LocalTime = LocalTime.parse(value, formatter) 13 | 14 | override fun canConvertTo(targetType: Class<*>) = LocalTime::class.java == targetType 15 | } 16 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/time/OffsetDateTimeConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import java.time.OffsetDateTime 4 | import java.time.format.DateTimeFormatter 5 | 6 | /** 7 | * This converter parses a String to the time format with offset from UTC 8 | */ 9 | open class OffsetDateTimeConverter : AbstractTemporalConverter() { 10 | 11 | override fun defaultFormatter(): DateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME 12 | override fun doParse(value: String, formatter: DateTimeFormatter): OffsetDateTime = 13 | OffsetDateTime.parse(value, formatter) 14 | 15 | override fun canConvertTo(targetType: Class<*>) = OffsetDateTime::class.java == targetType 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/kotlin/org/livingdoc/converters/time/ZonedDateTimeConverter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import java.time.ZonedDateTime 4 | import java.time.format.DateTimeFormatter 5 | 6 | /** 7 | * This converter parses a String to the time format with offset from UTC and a zone configuration regarding summer time 8 | */ 9 | open class ZonedDateTimeConverter : AbstractTemporalConverter() { 10 | 11 | override fun defaultFormatter(): DateTimeFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME 12 | override fun doParse(value: String, formatter: DateTimeFormatter): ZonedDateTime = 13 | ZonedDateTime.parse(value, formatter) 14 | 15 | override fun canConvertTo(targetType: Class<*>) = ZonedDateTime::class.java == targetType 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/resources/META-INF/services/org.livingdoc.api.conversion.TypeConverter: -------------------------------------------------------------------------------- 1 | org.livingdoc.converters.BooleanConverter 2 | org.livingdoc.converters.CharacterConverter 3 | org.livingdoc.converters.StringConverter 4 | 5 | org.livingdoc.converters.number.ByteConverter 6 | org.livingdoc.converters.number.ShortConverter 7 | org.livingdoc.converters.number.IntegerConverter 8 | org.livingdoc.converters.number.LongConverter 9 | org.livingdoc.converters.number.BigIntegerConverter 10 | org.livingdoc.converters.number.FloatConverter 11 | org.livingdoc.converters.number.DoubleConverter 12 | org.livingdoc.converters.number.BigDecimalConverter 13 | 14 | org.livingdoc.converters.time.LocalDateConverter 15 | org.livingdoc.converters.time.LocalTimeConverter 16 | org.livingdoc.converters.time.LocalDateTimeConverter 17 | org.livingdoc.converters.time.OffsetDateTimeConverter 18 | org.livingdoc.converters.time.ZonedDateTimeConverter 19 | 20 | org.livingdoc.converters.collection.ListConverter 21 | org.livingdoc.converters.collection.SetConverter 22 | org.livingdoc.converters.collection.MapConverter 23 | -------------------------------------------------------------------------------- /livingdoc-converters/src/main/resources/properties/color.properties: -------------------------------------------------------------------------------- 1 | # predefined name / rgb value pairs for colors. 2 | red = #FF0000 3 | blue = #0000FF 4 | yellow = #FFFF00 5 | green = #008000 6 | white = #FFFFFF 7 | black = #000000 -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/BooleanConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | 8 | class BooleanConverterJavaTest { 9 | 10 | BooleanConverter cut = new BooleanConverter(); 11 | 12 | @Test 13 | void converterCanConvertedToJavaBoolean() { 14 | assertThat(cut.canConvertTo(Boolean.class)).isTrue(); 15 | } 16 | 17 | @Test 18 | void javaInteroperabilityIsWorking() { 19 | Boolean value = cut.convert("true", null, null); 20 | assertThat(value).isTrue(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/CharacterConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | 8 | class CharacterConverterJavaTest { 9 | 10 | CharacterConverter cut = new CharacterConverter(); 11 | 12 | @Test 13 | void converterCanConvertedToJavaCharacter() { 14 | assertThat(cut.canConvertTo(Character.class)).isTrue(); 15 | } 16 | 17 | @Test 18 | void javaInteroperabilityIsWorking() { 19 | Character value = cut.convert("a", null, null); 20 | assertThat(value).isEqualTo('a'); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/StringConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | 8 | class StringConverterJavaTest { 9 | 10 | StringConverter cut = new StringConverter(); 11 | 12 | @Test 13 | void converterCanConvertedToJavaString() { 14 | assertThat(cut.canConvertTo(String.class)).isTrue(); 15 | } 16 | 17 | @Test 18 | void javaInteroperabilityIsWorking() { 19 | String value = cut.convert("abc", null, null); 20 | assertThat(value).isEqualTo("abc"); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/collection/ListConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import org.junit.jupiter.api.Test; 10 | import org.livingdoc.converters.TypeConvertersTestFixtures; 11 | 12 | 13 | class ListConverterJavaTest { 14 | 15 | ListConverter cut = new ListConverter(); 16 | 17 | @Test 18 | void converterCanConvertedToJavaList() { 19 | assertThat(cut.canConvertTo(List.class)).isTrue(); 20 | } 21 | 22 | @Test 23 | void javaInteroperabilityIsWorking() throws NoSuchFieldException { 24 | List expected = Arrays.asList(true, false, false, true); 25 | Field field = TypeConvertersTestFixtures.NoAnnotations.class.getField("listField"); 26 | 27 | List converted = cut.convert("true, false, false, true", field, null); 28 | assertThat(expected).isEqualTo(converted); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/collection/MapConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.AbstractMap.SimpleEntry; 7 | import java.util.Collections; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | 12 | import org.junit.jupiter.api.Test; 13 | import org.livingdoc.converters.TypeConvertersTestFixtures; 14 | 15 | 16 | class MapConverterJavaTest { 17 | 18 | MapConverter cut = new MapConverter(); 19 | 20 | @Test 21 | void converterCanConvertedToJavaMap() { 22 | assertThat(cut.canConvertTo(Map.class)).isTrue(); 23 | } 24 | 25 | @Test 26 | void javaInteroperabilityIsWorking() throws NoSuchFieldException { 27 | Map expected = Collections.unmodifiableMap( 28 | Stream.of(new SimpleEntry<>("true", true), new SimpleEntry<>("false", false)) 29 | .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue))); 30 | Field field = TypeConvertersTestFixtures.NoAnnotations.class.getField("mapField"); 31 | 32 | Map converted = cut.convert("true, true; false, false", field, null); 33 | assertThat(expected).isEqualTo(converted); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/collection/SetConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.livingdoc.converters.TypeConvertersTestFixtures; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.Arrays; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | 14 | class SetConverterJavaTest { 15 | 16 | SetConverter cut = new SetConverter(); 17 | 18 | @Test 19 | void converterCanConvertedToJavaSet() { 20 | assertThat(cut.canConvertTo(Set.class)).isTrue(); 21 | } 22 | 23 | @Test 24 | void javaInteroperabilityIsWorking() throws NoSuchFieldException { 25 | Set expected = new HashSet<>(Arrays.asList(true, false, false, true)); 26 | Field field = TypeConvertersTestFixtures.NoAnnotations.class.getField("setField"); 27 | 28 | Set converted = cut.convert("true, false, false, true", field, null); 29 | assertThat(expected).isEqualTo(converted); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/number/BigDecimalConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import utils.EnglishDefaultLocale; 10 | 11 | 12 | @EnglishDefaultLocale 13 | class BigDecimalConverterJavaTest { 14 | 15 | BigDecimalConverter cut = new BigDecimalConverter(); 16 | 17 | @Test 18 | void converterCanConvertedToJavaBigDecimal() { 19 | assertThat(cut.canConvertTo(BigDecimal.class)).isTrue(); 20 | } 21 | 22 | @Test 23 | void javaInteroperabilityIsWorking() { 24 | BigDecimal value = cut.convert("42.01", null, null); 25 | assertThat(value).isEqualTo(BigDecimal.valueOf(42.01d)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/number/BigIntegerConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.math.BigInteger; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import utils.EnglishDefaultLocale; 10 | 11 | 12 | @EnglishDefaultLocale 13 | class BigIntegerConverterJavaTest { 14 | 15 | BigIntegerConverter cut = new BigIntegerConverter(); 16 | 17 | @Test 18 | void converterCanConvertedToJavaBigInteger() { 19 | assertThat(cut.canConvertTo(BigInteger.class)).isTrue(); 20 | } 21 | 22 | @Test 23 | void javaInteroperabilityIsWorking() { 24 | BigInteger value = cut.convert("42", null, null); 25 | assertThat(value).isEqualTo(BigInteger.valueOf(42L)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/number/ByteConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import utils.EnglishDefaultLocale; 8 | 9 | 10 | @EnglishDefaultLocale 11 | class ByteConverterJavaTest { 12 | 13 | ByteConverter cut = new ByteConverter(); 14 | 15 | @Test 16 | void converterCanConvertedToJavaByte() { 17 | assertThat(cut.canConvertTo(Byte.class)).isTrue(); 18 | } 19 | 20 | @Test 21 | void javaInteroperabilityIsWorking() { 22 | Byte value = cut.convert("42", null, null); 23 | assertThat(value).isEqualTo(( byte ) 42); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/number/DoubleConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import utils.EnglishDefaultLocale; 8 | 9 | 10 | @EnglishDefaultLocale 11 | class DoubleConverterJavaTest { 12 | 13 | DoubleConverter cut = new DoubleConverter(); 14 | 15 | @Test 16 | void converterCanConvertedToJavaDouble() { 17 | assertThat(cut.canConvertTo(Double.class)).isTrue(); 18 | } 19 | 20 | @Test 21 | void javaInteroperabilityIsWorking() { 22 | Double value = cut.convert("42.01", null, null); 23 | assertThat(value).isEqualTo(42.01d); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/number/FloatConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import utils.EnglishDefaultLocale; 8 | 9 | 10 | @EnglishDefaultLocale 11 | class FloatConverterJavaTest { 12 | 13 | FloatConverter cut = new FloatConverter(); 14 | 15 | @Test 16 | void converterCanConvertedToJavaFloat() { 17 | assertThat(cut.canConvertTo(Float.class)).isTrue(); 18 | } 19 | 20 | @Test 21 | void javaInteroperabilityIsWorking() { 22 | Float value = cut.convert("42.01", null, null); 23 | assertThat(value).isEqualTo(42.01f); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/number/IntegerConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import utils.EnglishDefaultLocale; 8 | 9 | 10 | @EnglishDefaultLocale 11 | class IntegerConverterJavaTest { 12 | 13 | IntegerConverter cut = new IntegerConverter(); 14 | 15 | @Test 16 | void converterCanConvertedToJavaInteger() { 17 | assertThat(cut.canConvertTo(Integer.class)).isTrue(); 18 | } 19 | 20 | @Test 21 | void javaInteroperabilityIsWorking() { 22 | Integer value = cut.convert("42", null, null); 23 | assertThat(value).isEqualTo(42); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/number/LongConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import utils.EnglishDefaultLocale; 8 | 9 | 10 | @EnglishDefaultLocale 11 | class LongConverterJavaTest { 12 | 13 | LongConverter cut = new LongConverter(); 14 | 15 | @Test 16 | void converterCanConvertedToJavaLong() { 17 | assertThat(cut.canConvertTo(Long.class)).isTrue(); 18 | } 19 | 20 | @Test 21 | void javaInteroperabilityIsWorking() { 22 | Long value = cut.convert("42", null, null); 23 | assertThat(value).isEqualTo(42L); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/number/ShortConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import utils.EnglishDefaultLocale; 8 | 9 | 10 | @EnglishDefaultLocale 11 | class ShortConverterJavaTest { 12 | 13 | ShortConverter cut = new ShortConverter(); 14 | 15 | @Test 16 | void converterCanConvertedToJavaShort() { 17 | assertThat(cut.canConvertTo(Short.class)).isTrue(); 18 | } 19 | 20 | @Test 21 | void javaInteroperabilityIsWorking() { 22 | Short value = cut.convert("42", null, null); 23 | assertThat(value).isEqualTo(( short ) 42); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/time/LocalDateConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.time.LocalDate; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | 10 | class LocalDateConverterJavaTest { 11 | 12 | LocalDateConverter cut = new LocalDateConverter(); 13 | 14 | @Test 15 | void converterCanConvertedToJavaLocalDate() { 16 | assertThat(cut.canConvertTo(LocalDate.class)).isTrue(); 17 | } 18 | 19 | @Test 20 | void javaInteroperabilityIsWorking() { 21 | LocalDate now = LocalDate.now(); 22 | LocalDate value = cut.convert(now.toString(), null, null); 23 | assertThat(value).isEqualTo(now); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/time/LocalDateTimeConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | 10 | class LocalDateTimeConverterJavaTest { 11 | 12 | LocalDateTimeConverter cut = new LocalDateTimeConverter(); 13 | 14 | @Test 15 | void converterCanConvertedToJavaLocalDateTime() { 16 | assertThat(cut.canConvertTo(LocalDateTime.class)).isTrue(); 17 | } 18 | 19 | @Test 20 | void javaInteroperabilityIsWorking() { 21 | LocalDateTime now = LocalDateTime.now(); 22 | LocalDateTime value = cut.convert(now.toString(), null, null); 23 | assertThat(value).isEqualTo(now); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/time/LocalTimeConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.time.LocalTime; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | 10 | class LocalTimeConverterJavaTest { 11 | 12 | LocalTimeConverter cut = new LocalTimeConverter(); 13 | 14 | @Test 15 | void converterCanConvertedToJavaLocalTime() { 16 | assertThat(cut.canConvertTo(LocalTime.class)).isTrue(); 17 | } 18 | 19 | @Test 20 | void javaInteroperabilityIsWorking() { 21 | LocalTime now = LocalTime.now(); 22 | LocalTime value = cut.convert(now.toString(), null, null); 23 | assertThat(value).isEqualTo(now); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/time/OffsetDateTimeConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.time.OffsetDateTime; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | 10 | class OffsetDateTimeConverterJavaTest { 11 | 12 | OffsetDateTimeConverter cut = new OffsetDateTimeConverter(); 13 | 14 | @Test 15 | void converterCanConvertedToJavaOffsetDateTime() { 16 | assertThat(cut.canConvertTo(OffsetDateTime.class)).isTrue(); 17 | } 18 | 19 | @Test 20 | void javaInteroperabilityIsWorking() { 21 | OffsetDateTime now = OffsetDateTime.now(); 22 | OffsetDateTime value = cut.convert(now.toString(), null, null); 23 | assertThat(value).isEqualTo(now); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/java/org/livingdoc/converters/time/ZonedDateTimeConverterJavaTest.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.time.ZonedDateTime; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | 10 | class ZonedDateTimeConverterJavaTest { 11 | 12 | ZonedDateTimeConverter cut = new ZonedDateTimeConverter(); 13 | 14 | @Test 15 | void converterCanConvertedToJavaZonedDateTime() { 16 | assertThat(cut.canConvertTo(ZonedDateTime.class)).isTrue(); 17 | } 18 | 19 | @Test 20 | void javaInteroperabilityIsWorking() { 21 | ZonedDateTime now = ZonedDateTime.now(); 22 | ZonedDateTime value = cut.convert(now.toString(), null, null); 23 | assertThat(value).isEqualTo(now); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/DefaultTypeConverterContract.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.api.conversion.TypeConverter 6 | 7 | interface DefaultTypeConverterContract { 8 | 9 | val cut: TypeConverter 10 | 11 | @Test fun `converter is automatically loaded into manager`() { 12 | val converterOfType = TypeConverterManager.getDefaultConverters() 13 | .first { it.javaClass == cut.javaClass } 14 | assertThat(converterOfType).isNotNull() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/StringConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.params.ParameterizedTest 6 | import org.junit.jupiter.params.provider.ValueSource 7 | 8 | internal class StringConverterTest : DefaultTypeConverterContract { 9 | 10 | override val cut = StringConverter() 11 | 12 | @ParameterizedTest(name = "\"{0}\"") 13 | @ValueSource(strings = ["", " ", "foo", "foo bar"]) 14 | fun `any string value is returned as is`(value: String) { 15 | assertThat(cut.convert(value, null, null)).isEqualTo(value) 16 | } 17 | 18 | @Test fun `converter can converted to Kotlin String`() { 19 | assertThat(cut.canConvertTo(String::class.java)).isTrue() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/collection/ListConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection 2 | 3 | import org.livingdoc.api.conversion.Converter 4 | 5 | internal class ListConverterTest : CollectionConverterContract() { 6 | 7 | override val cut = ListConverter() 8 | override val collectionClass = List::class.java 9 | override val fixtureClass = ListFake::class 10 | override val booleanInput: String = "true, false, false, true" 11 | override val intInput: String = "1, 2, 3, 4" 12 | override val intExpectation = listOf(1, 2, 3, 4) 13 | override val booleanExpectation = listOf(true, false, false, true) 14 | 15 | @Suppress("unused", "UNUSED_PARAMETER") 16 | internal class ListFake { 17 | 18 | @Converter(ListConverter::class) 19 | lateinit var integer: List 20 | 21 | fun boolean(@Converter(ListConverter::class) value: List) {} 22 | 23 | fun noType(@Converter(ListConverter::class) value: List) {} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/collection/SetConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.collection 2 | 3 | import org.livingdoc.api.conversion.Converter 4 | 5 | internal class SetConverterTest : CollectionConverterContract() { 6 | 7 | override val cut = SetConverter() 8 | override val collectionClass = Set::class.java 9 | override val fixtureClass = SetFake::class 10 | override val booleanInput: String = "true, false, false, true" 11 | override val intInput: String = "1, 2, 3, 4" 12 | override val intExpectation = setOf(1, 2, 3, 4) 13 | override val booleanExpectation = setOf(true, false, false, true) 14 | 15 | @Suppress("unused", "UNUSED_PARAMETER") 16 | internal class SetFake { 17 | 18 | lateinit var integer: Set 19 | 20 | fun boolean(value: Set) {} 21 | 22 | fun noType(@Converter(SetConverter::class) value: Set) {} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/number/BigDecimalConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | import java.math.BigDecimal 7 | 8 | internal class BigDecimalConverterTest : NumberConverterContract(), DefaultTypeConverterContract { 9 | 10 | override val cut = BigDecimalConverter() 11 | 12 | override val minValue = BigDecimal.valueOf(Double.MIN_VALUE) 13 | override val negativeValue = BigDecimal.valueOf(-42.24) 14 | override val zeroValue = BigDecimal.ZERO 15 | override val positiveValue = BigDecimal.valueOf(42.24) 16 | override val maxValue = BigDecimal.valueOf(Double.MAX_VALUE) 17 | 18 | override val englishValue = "42,000,000,000,000.12" to BigDecimal("42000000000000.12") 19 | override val germanValue = "42.000.000.000.000,12" to BigDecimal("42000000000000.12") 20 | 21 | @Test fun `converter can converted to BigDecimal`() { 22 | assertThat(cut.canConvertTo(BigDecimal::class.java)).isTrue() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/number/BigIntegerConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | import java.math.BigInteger 7 | 8 | internal class BigIntegerConverterTest : NumberConverterContract(), DefaultTypeConverterContract { 9 | 10 | override val cut = BigIntegerConverter() 11 | 12 | override val minValue = BigInteger.valueOf(Long.MIN_VALUE) 13 | override val negativeValue = BigInteger.valueOf(-42L) 14 | override val zeroValue = BigInteger.ZERO 15 | override val positiveValue = BigInteger.valueOf(42L) 16 | override val maxValue = BigInteger.valueOf(Long.MAX_VALUE) 17 | 18 | override val englishValue = "42,000,000,000,000.12" to BigInteger("42000000000000") 19 | override val germanValue = "42.000.000.000.000,12" to BigInteger("42000000000000") 20 | 21 | @Test fun `converter can converted to BigInteger`() { 22 | assertThat(cut.canConvertTo(BigInteger::class.java)).isTrue() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/number/ByteConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | 7 | internal class ByteConverterTest : BoundedNumberConverterContract(), DefaultTypeConverterContract { 8 | 9 | override val cut = ByteConverter() 10 | 11 | override val minValue = Byte.MIN_VALUE 12 | override val negativeValue = (-42).toByte() 13 | override val zeroValue = 0.toByte() 14 | override val positiveValue = 42.toByte() 15 | override val maxValue = Byte.MAX_VALUE 16 | 17 | override val englishValue = "100.12" to 100.toByte() 18 | override val germanValue = "100,12" to 100.toByte() 19 | 20 | @Test fun `converter can converted to Kotlin Byte`() { 21 | assertThat(cut.canConvertTo(Byte::class.java)).isTrue() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/number/IntegerConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | 7 | internal class IntegerConverterTest : BoundedNumberConverterContract(), DefaultTypeConverterContract { 8 | 9 | override val cut = IntegerConverter() 10 | 11 | override val minValue = Int.MIN_VALUE 12 | override val negativeValue = -42 13 | override val zeroValue = 0 14 | override val positiveValue = 42 15 | override val maxValue = Int.MAX_VALUE 16 | 17 | override val englishValue = "42,000.12" to 42000 18 | override val germanValue = "42.000,12" to 42000 19 | 20 | @Test fun `converter can converted to Kotlin Int`() { 21 | assertThat(cut.canConvertTo(Int::class.java)).isTrue() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/number/LongConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | 7 | internal class LongConverterTest : BoundedNumberConverterContract(), DefaultTypeConverterContract { 8 | 9 | override val cut = LongConverter() 10 | 11 | override val minValue = Long.MIN_VALUE 12 | override val negativeValue = -42L 13 | override val zeroValue = 0L 14 | override val positiveValue = 42L 15 | override val maxValue = Long.MAX_VALUE 16 | 17 | override val englishValue = "42,000.12" to 42000L 18 | override val germanValue = "42.000,12" to 42000L 19 | 20 | @Test fun `converter can converted to Kotlin Long`() { 21 | assertThat(cut.canConvertTo(Long::class.java)).isTrue() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/number/ShortConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.number 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | 7 | internal class ShortConverterTest : BoundedNumberConverterContract(), DefaultTypeConverterContract { 8 | 9 | override val cut = ShortConverter() 10 | 11 | override val minValue = Short.MIN_VALUE 12 | override val negativeValue = (-42).toShort() 13 | override val zeroValue = 0.toShort() 14 | override val positiveValue = 42.toShort() 15 | override val maxValue = Short.MAX_VALUE 16 | 17 | override val englishValue = "10,000.12" to 10000.toShort() 18 | override val germanValue = "10.000,12" to 10000.toShort() 19 | 20 | @Test fun `converter can converted to Kotlin Short`() { 21 | assertThat(cut.canConvertTo(Short::class.java)).isTrue() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/time/LocalDateConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | import java.time.LocalDate 7 | import java.time.LocalDate.parse 8 | 9 | internal class LocalDateConverterTest : TemporalConverterContract(), DefaultTypeConverterContract { 10 | 11 | override val cut = LocalDateConverter() 12 | 13 | override val validInputVariations = mapOf( 14 | "2017-05-12" to parse("2017-05-12") 15 | ) 16 | 17 | override val defaultFormatValue = "2017-05-12" to parse("2017-05-12") 18 | 19 | override val customFormat = "dd.MM.uuuu" 20 | override val customFormatValue = "12.05.2017" to parse("2017-05-12") 21 | override val malformedCustomFormat = "dd.MM.uuuu V" 22 | 23 | @Test fun `converter can converted to LocalDate`() { 24 | assertThat(cut.canConvertTo(LocalDate::class.java)).isTrue() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/time/LocalDateTimeConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | import java.time.LocalDateTime 7 | import java.time.LocalDateTime.parse 8 | 9 | internal class LocalDateTimeConverterTest : TemporalConverterContract(), DefaultTypeConverterContract { 10 | 11 | override val cut = LocalDateTimeConverter() 12 | 13 | override val validInputVariations = mapOf( 14 | "2017-05-12T12:34" to parse("2017-05-12T12:34"), 15 | "2017-05-12T12:34:56" to parse("2017-05-12T12:34:56"), 16 | "2017-05-12T12:34:56.123" to parse("2017-05-12T12:34:56.123") 17 | ) 18 | 19 | override val defaultFormatValue = "2017-05-12T12:34" to parse("2017-05-12T12:34") 20 | 21 | override val customFormat = "dd.MM.uuuu HH:mm 'Uhr'" 22 | override val customFormatValue = "12.05.2017 12:34 Uhr" to parse("2017-05-12T12:34") 23 | override val malformedCustomFormat = "dd.MM.uuuu HH:mm V" 24 | 25 | @Test fun `converter can converted to LocalDateTime`() { 26 | assertThat(cut.canConvertTo(LocalDateTime::class.java)).isTrue() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/time/LocalTimeConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | import java.time.LocalTime 7 | import java.time.LocalTime.parse 8 | 9 | internal class LocalTimeConverterTest : TemporalConverterContract(), DefaultTypeConverterContract { 10 | 11 | override val cut = LocalTimeConverter() 12 | 13 | override val validInputVariations = mapOf( 14 | "12:34" to parse("12:34"), 15 | "12:34:56" to parse("12:34:56") 16 | ) 17 | 18 | override val defaultFormatValue = "12:34" to parse("12:34") 19 | 20 | override val customFormat = "HH:mm 'Uhr'" 21 | override val customFormatValue = "12:34 Uhr" to parse("12:34:00") 22 | override val malformedCustomFormat = "HH:mm V" 23 | 24 | @Test fun `converter can converted to LocalTime`() { 25 | assertThat(cut.canConvertTo(LocalTime::class.java)).isTrue() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/org/livingdoc/converters/time/OffsetDateTimeConverterTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.converters.time 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.converters.DefaultTypeConverterContract 6 | import java.time.OffsetDateTime 7 | import java.time.OffsetDateTime.parse 8 | 9 | internal class OffsetDateTimeConverterTest : TemporalConverterContract(), DefaultTypeConverterContract { 10 | 11 | override val cut = OffsetDateTimeConverter() 12 | 13 | override val validInputVariations = mapOf( 14 | "2017-05-12T12:34+01:00" to parse("2017-05-12T12:34+01:00"), 15 | "2017-05-12T12:34:56+01:00" to parse("2017-05-12T12:34:56+01:00"), 16 | "2017-05-12T12:34:56.123+01:00" to parse("2017-05-12T12:34:56.123+01:00") 17 | ) 18 | 19 | override val defaultFormatValue = "2017-05-12T12:34+01:00" to parse("2017-05-12T12:34+01:00") 20 | 21 | override val customFormat = "dd.MM.uuuu HH:mm 'Uhr' X" 22 | override val customFormatValue = "12.05.2017 12:34 Uhr +01" to parse("2017-05-12T12:34+01:00") 23 | override val malformedCustomFormat = "dd.MM.uuuu HH:mm X V" 24 | 25 | @Test fun `converter can converted to OffsetDateTime`() { 26 | assertThat(cut.canConvertTo(OffsetDateTime::class.java)).isTrue() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/kotlin/utils/EnglishDefaultLocale.kt: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith 4 | 5 | @Target(AnnotationTarget.CLASS) 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @ExtendWith(EnglishDefaultLocaleExtension::class) 8 | annotation class EnglishDefaultLocale 9 | -------------------------------------------------------------------------------- /livingdoc-converters/src/test/resources/colorData.csv: -------------------------------------------------------------------------------- 1 | input=expected 2 | #66ff33=#66ff33 3 | #00bfff=#00bfff 4 | #000 = #000000 5 | #abc = #aabbcc 6 | #fff = #ffffff 7 | #FFF = #ffffff 8 | green=#008000 9 | blue=#0000ff 10 | rgb( 122, 234, 100)=#7aea64 11 | rgb(0,191,255)=#00bfff 12 | r gb(0,191,255) =#00bfff 13 | rgb(0, 191, 255) = #00bfff 14 | rgb(10,191,255) = #0abfff 15 | rgb(15,145,234) = #0f91ea 16 | rgb(16,145,234) = #1091ea -------------------------------------------------------------------------------- /livingdoc-converters/src/test/resources/invalidColorData.csv: -------------------------------------------------------------------------------- 1 | input 2 | #2P9k02 3 | darg 4 | pink 5 | rgb(180,180,456) 6 | #1 7 | #dddd 8 | #c2 9 | #67abf 10 | rgb( , , ) 11 | rgb(180) 12 | rgb(180,180) 13 | rgb(180,180,) 14 | rgb(,180,180,180) 15 | rgb(,180) 16 | rgb(,180,180,) 17 | rgb(,180,) 18 | rgb(180,180,180 19 | rgb 180,180,180) 20 | rb(180,180,180) -------------------------------------------------------------------------------- /livingdoc-documentation/livingdoc-documentation.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.asciidoctor.convert") 3 | `java-project-config` 4 | } 5 | 6 | dependencies { 7 | compile(project(":livingdoc-api")) 8 | compile("org.assertj:assertj-core:${Versions.assertJ}") 9 | } 10 | 11 | tasks { 12 | asciidoctor { 13 | // Documentation: http://asciidoctor.org/docs/asciidoctor-gradle-plugin/ 14 | sources(delegateClosureOf { 15 | include("index.adoc") 16 | }) 17 | options(mapOf("doctype" to "book", "backend" to "html5")) 18 | attributes( 19 | mapOf( 20 | "moduleBase" to "${projectDir}", 21 | "source-highlighter" to "coderay", 22 | "toc" to "left", 23 | "toclevels" to "3", 24 | "sectlinks" to "true", 25 | "sectnums" to "true" 26 | ) 27 | ) 28 | } 29 | } 30 | 31 | //tasks.build.dependsOn asciidoctor 32 | -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/business-expert/index.adoc: -------------------------------------------------------------------------------- 1 | = Documentation for business experts 2 | :toc: 3 | 4 | == Introduction 5 | This page is intended to provide a detailed explanation of how to use LivingDoc in the role of a business expert. 6 | 7 | include::writing-testdata.adoc[] 8 | 9 | include::creating-testdata.adoc[] 10 | 11 | include::test-reports.adoc[] 12 | -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/business-expert/pic/MANUAL-in-fixture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LivingDoc/livingdoc/f3d52b8bacbdf81905e4b4a753d75f584329b297/livingdoc-documentation/src/docs/asciidoc/business-expert/pic/MANUAL-in-fixture.png -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/business-expert/pic/execution-report-confluence-page.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LivingDoc/livingdoc/f3d52b8bacbdf81905e4b4a753d75f584329b297/livingdoc-documentation/src/docs/asciidoc/business-expert/pic/execution-report-confluence-page.PNG -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/business-expert/pic/execution-report-confluence-result.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LivingDoc/livingdoc/f3d52b8bacbdf81905e4b4a753d75f584329b297/livingdoc-documentation/src/docs/asciidoc/business-expert/pic/execution-report-confluence-result.PNG -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/business-expert/pic/scenario-final-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LivingDoc/livingdoc/f3d52b8bacbdf81905e4b4a753d75f584329b297/livingdoc-documentation/src/docs/asciidoc/business-expert/pic/scenario-final-view.png -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/business-expert/pic/table-multiple-columns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LivingDoc/livingdoc/f3d52b8bacbdf81905e4b4a753d75f584329b297/livingdoc-documentation/src/docs/asciidoc/business-expert/pic/table-multiple-columns.png -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/business-expert/pic/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LivingDoc/livingdoc/f3d52b8bacbdf81905e4b4a753d75f584329b297/livingdoc-documentation/src/docs/asciidoc/business-expert/pic/view.png -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/configuration.adoc: -------------------------------------------------------------------------------- 1 | == LivingDoc Configuration 2 | 3 | The `living-doc.yaml` contains the yaml based configuration of LivingDoc. 4 | The configuration contains the configuration of link:repositories.adoc[Repositories] and link:reports.adoc[Reports]. 5 | The configuration file should be placed in the `src/test/resources` directory, so it is in the classpath when running the tests. 6 | 7 | A different configuration file can be specified using the Java System Property `livingdoc.config`. 8 | Depending on the build system used and the test configuration, the Java System Property have to be specified in a different way and may require changing the build configuration. 9 | In the simplest case the property can be passed on the command line using the `-D` option of `java`, `mvn` or `gradle`. 10 | For example use `gradle test -Dlivingdoc.config=dev-livingdoc.yaml` to execute the tests with the `dev-livingdoc.yaml` configuration file. 11 | This only works if the tests run in the same JVM and not as a forked process. 12 | -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/fixtures.adoc: -------------------------------------------------------------------------------- 1 | == Writing Fixture Code 2 | 3 | In the following, the supported fixtures are explained. 4 | 5 | include::fixtures-decision-tables.adoc[] 6 | 7 | include::fixtures-scenarios.adoc[] 8 | 9 | === Special Cases 10 | This section shows some details that are valid for decision tables and scenarios. 11 | 12 | ==== Expecting Exceptions as Output 13 | Some inputs lead to an `Exception` being thrown. 14 | As a business expert, you can expect this to happen by using the keyword `error` as expected value in the 15 | decision table or scenario. 16 | If an `Exception` is thrown and expected, the test is marked as executed successfully. 17 | 18 | ==== Reporting the actual result 19 | If you do not really know what to expect in the decision table, you can just leave the field with 20 | the expected value empty and LivingDoc reports the result of the fixture. 21 | 22 | This means that error ("error") and an empty value ("") are reserved keywords for LivingDoc and these strings cannot be used as expected strings. 23 | -------------------------------------------------------------------------------- /livingdoc-documentation/src/docs/asciidoc/index.adoc: -------------------------------------------------------------------------------- 1 | = LivingDoc Documentation 2 | :toc: 3 | 4 | == Introduction 5 | The next version of LivingDoc, the acceptance testing tool with integrated 6 | support for Atlassian Confluence. 7 | 8 | The following document is a starting point to get ready and use LivingDoc 2. 9 | It provides you with examples of how you need to configure your environment and how to write fixtures. 10 | 11 | include::configuration.adoc[] 12 | 13 | include::repositories.adoc[] 14 | 15 | include::fixtures.adoc[] 16 | 17 | include::annotate-tests.adoc[] 18 | 19 | include::reports.adoc[] 20 | -------------------------------------------------------------------------------- /livingdoc-documentation/src/main/java/examples/decisiontables/CalculatorFixture.java: -------------------------------------------------------------------------------- 1 | package examples.decisiontables; 2 | 3 | import implementations.Calculator; 4 | import org.livingdoc.api.fixtures.decisiontables.Check; 5 | import org.livingdoc.api.fixtures.decisiontables.DecisionTableFixture; 6 | import org.livingdoc.api.fixtures.decisiontables.Input; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | import static org.assertj.core.data.Offset.offset; 10 | 11 | /** 12 | * This example demonstrates a decision table fixture and its annotation possibility. Other than in the sample 13 | * we have no executable document. 14 | */ 15 | 16 | @DecisionTableFixture(value ="Calculator", parallel = true) 17 | public class CalculatorFixture { 18 | 19 | Calculator sut = new Calculator(); 20 | 21 | @Input("value a") 22 | Double valueA; 23 | @Input("value b") 24 | Double valueB; 25 | 26 | @Check("a + b = ?") 27 | void checkSum(Double expectedValue) { 28 | double actualValue = sut.sum(valueA, valueB); 29 | assertThat(actualValue).isEqualTo(expectedValue, offset(0.000000001d)); 30 | } 31 | 32 | @Check("a - b = ?") 33 | void checkDiff(Double expectedValue) { 34 | double actualValue = sut.diff(valueA, valueB); 35 | assertThat(actualValue).isEqualTo(expectedValue, offset(0.000000001d)); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /livingdoc-documentation/src/main/java/examples/scenarios/CalculatorFixture.java: -------------------------------------------------------------------------------- 1 | package examples.scenarios; 2 | 3 | import implementations.Calculator; 4 | import org.livingdoc.api.After; 5 | import org.livingdoc.api.Before; 6 | import org.livingdoc.api.fixtures.scenarios.Binding; 7 | import org.livingdoc.api.fixtures.scenarios.ScenarioFixture; 8 | import org.livingdoc.api.fixtures.scenarios.Step; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | /** 13 | * This example demonstrates a scenario fixture and its annotation possibility. Other than in the sample 14 | * we have no executable document. 15 | */ 16 | 17 | @ScenarioFixture("Calculator") 18 | public class CalculatorFixture { 19 | 20 | private Calculator sut; 21 | 22 | @Before 23 | private void before(){ 24 | sut = new Calculator(); 25 | } 26 | 27 | @Step("adding {a} and {b} equals {c}") 28 | void add(@Binding("a") Float a, @Binding("b") Float b, @Binding("c") Float c) { 29 | double result = sut.sum(a, b); 30 | assertThat(result).isEqualTo(c); 31 | } 32 | 33 | @After 34 | private void after(){ 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /livingdoc-documentation/src/main/java/implementations/Calculator.java: -------------------------------------------------------------------------------- 1 | package implementations; 2 | 3 | /** 4 | * A simple calculator, just as in the sample but here written in JAVA 5 | * 6 | * This will be our first system under test (SUT) in this project 7 | */ 8 | 9 | public class Calculator { 10 | 11 | /** 12 | * @param a the dividend 13 | * @param b the divisor 14 | * @return the quotient of two numbers 15 | */ 16 | 17 | public double sum(double a, double b) { 18 | return a + b; 19 | } 20 | 21 | /** 22 | * @param a the minuend 23 | * @param b the subtrahend 24 | * @return the difference of two numbers 25 | */ 26 | 27 | public double diff(double a, double b) { 28 | return a - b; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /livingdoc-engine/livingdoc-engine.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | id("me.champeau.gradle.jmh").version("0.5.0") 4 | } 5 | 6 | dependencies { 7 | implementation("org.slf4j:slf4j-api:${Versions.slf4j}") 8 | implementation(project(":livingdoc-api")) 9 | implementation(project(":livingdoc-converters")) 10 | implementation(project(":livingdoc-extensions-api")) 11 | implementation(project(":livingdoc-results")) 12 | implementation(project(":livingdoc-testdata")) 13 | implementation(project(":livingdoc-reports")) 14 | 15 | api(project(":livingdoc-config")) 16 | api(project(":livingdoc-repositories")) 17 | 18 | testImplementation("ch.qos.logback:logback-classic:${Versions.logback}") 19 | testImplementation("org.assertj:assertj-core:${Versions.assertJ}") 20 | } 21 | 22 | tasks.compileTestJava { 23 | options.compilerArgs.add("-parameters") 24 | } 25 | 26 | jmh { 27 | benchmarkMode = listOf("AverageTime", "SampleTime") 28 | duplicateClassesStrategy = DuplicatesStrategy.WARN 29 | fork = 1 30 | timeUnit = "ms" 31 | 32 | } 33 | -------------------------------------------------------------------------------- /livingdoc-engine/src/jmh/kotlin/org/livingdoc/engine/execution/examples/decisiontables/BaselineBenchmark.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.examples.decisiontables 2 | 3 | import org.openjdk.jmh.annotations.Benchmark 4 | 5 | open class BaselineBenchmark { 6 | @Benchmark 7 | fun baseline() { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/config/TaggingConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.config 2 | 3 | import org.livingdoc.config.ConfigProvider 4 | 5 | data class TaggingConfig(var tags: TaggingDefinition = TaggingDefinition()) { 6 | companion object { 7 | fun from(configProvider: ConfigProvider): TaggingConfig { 8 | return configProvider.getConfigAs("tags", TaggingConfig::class) 9 | } 10 | } 11 | 12 | val includedTags: List 13 | get() = System.getProperty("livingdoc.tags.include")?.split(',') ?: tags.include 14 | val excludedTags: List 15 | get() = System.getProperty("livingdoc.tags.exclude")?.split(',') ?: tags.exclude 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/config/TaggingDefinition.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.config 2 | 3 | /** 4 | * This data class contains two lists, one for included string tags, one for excluded string tags 5 | */ 6 | data class TaggingDefinition( 7 | var include: List = emptyList(), 8 | var exclude: List = emptyList() 9 | ) 10 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/ExecutionException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution 2 | 3 | /** 4 | * Exceptions of this type are thrown in case an execution failed in a way that did not allow for a [Result] to be 5 | * produced. 6 | */ 7 | open class ExecutionException : RuntimeException() 8 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/MalformedFixtureException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution 2 | 3 | import java.lang.RuntimeException 4 | 5 | /** 6 | * MalformedFixtureException is thrown when a fixture does not parse validation 7 | */ 8 | internal class MalformedFixtureException(fixtureClass: Class<*>, errors: List) : 9 | RuntimeException( 10 | "The fixture class <$fixtureClass> is malformed: \n${errors.joinToString( 11 | separator = "\n", 12 | prefix = " - " 13 | )}") 14 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/ScopedFixtureModel.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution 2 | 3 | import org.livingdoc.api.After 4 | import org.livingdoc.api.Before 5 | import java.lang.reflect.Method 6 | 7 | /** 8 | * ScopedFixtureModel represents a fixture that can have [Before] and [After] methods 9 | */ 10 | internal open class ScopedFixtureModel( 11 | private val fixtureClass: Class<*> 12 | ) { 13 | val beforeMethods: List = fixtureClass.declaredMethods.filter { method -> 14 | method.isAnnotationPresent(Before::class.java) 15 | }.sortedBy { method -> method.name } 16 | 17 | val afterMethods: List = fixtureClass.declaredMethods.filter { method -> 18 | method.isAnnotationPresent(After::class.java) 19 | }.sortedBy { method -> method.name } 20 | } 21 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/documents/DocumentIdentifier.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.documents 2 | 3 | /** 4 | * A DocumentIdentifier contains the storage location and name of a single document 5 | */ 6 | internal data class DocumentIdentifier( 7 | val repository: String, 8 | val id: String 9 | ) { 10 | companion object { 11 | /** 12 | * of creates a DocumentIdentifier from a [DocumentFixture]. 13 | * 14 | * @param document the [DocumentFixture] for which to create the DocumentIdentifier 15 | * @return the DocumentIdentifier for the given [DocumentFixture] 16 | */ 17 | fun of(document: DocumentFixture): DocumentIdentifier { 18 | val annotation = document.executableDocumentAnnotation!! 19 | val values = annotation.value.split("://") 20 | .also { require(it.size == 2) { "Illegal annotation value '${annotation.value}'." } } 21 | return DocumentIdentifier(values[0], values[1]) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/NoExpectedExceptionThrownException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.examples 2 | 3 | /** 4 | * This exception is thrown whenever an exception was expected but not thrown 5 | */ 6 | internal class NoExpectedExceptionThrownException : AssertionError( 7 | "No exception thrown but exception was expected to be thrown by fixture" 8 | ) 9 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/scenarios/ScenarioNoFixture.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.examples.scenarios 2 | 3 | import org.livingdoc.engine.LivingDoc 4 | import org.livingdoc.engine.fixtures.Fixture 5 | import org.livingdoc.repositories.model.scenario.Scenario 6 | import org.livingdoc.results.Status 7 | import org.livingdoc.results.examples.scenarios.ScenarioResult 8 | 9 | class ScenarioNoFixture : Fixture { 10 | /** 11 | * Executes the given test data as a manual test 12 | * 13 | * Does not throw any kind of exception. 14 | * Exceptional state of the execution is packaged inside the [ScenarioResult] in 15 | * the form of different status objects. 16 | * 17 | * @param testData Test data of the corresponding type 18 | */ 19 | override fun execute(testData: Scenario): ScenarioResult { 20 | val resultBuilder = ScenarioResult.Builder() 21 | .withScenario(testData).withFixtureSource(this.javaClass) 22 | 23 | if (LivingDoc.failFastActivated) { 24 | return resultBuilder.withStatus( 25 | Status.Skipped 26 | ).build() 27 | } 28 | 29 | if (testData.description.isManual) { 30 | resultBuilder.withStatus(Status.Manual) 31 | } 32 | 33 | return resultBuilder.build() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/examples/scenarios/matching/ScenarioStepMatcher.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.examples.scenarios.matching 2 | 3 | // maybe refactor this exception to yield more information 4 | class NoMatchingStepTemplate(msg: String) : RuntimeException(msg) 5 | 6 | internal class ScenarioStepMatcher(private val stepTemplates: List) { 7 | 8 | data class MatchingResult( 9 | val template: StepTemplate, 10 | val variables: Map 11 | ) 12 | 13 | fun match(step: String): MatchingResult { 14 | 15 | if (stepTemplates.isEmpty()) 16 | throw NoMatchingStepTemplate("StepTemplate list must not be empty") 17 | 18 | val bestFit = stepTemplates 19 | .map { it.alignWith(step, maxLevelOfStemming = 4.0f) } 20 | .sortedWith(compareBy({ it.totalCost.first }, { it.totalCost.second })) 21 | .first() 22 | 23 | if (bestFit.isMisaligned()) { 24 | throw NoMatchingStepTemplate("No matching template!") 25 | } 26 | 27 | return MatchingResult(bestFit.stepTemplate, bestFit.variables) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/groups/GroupFixtureModel.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.groups 2 | 3 | import org.livingdoc.api.documents.FailFast 4 | import org.livingdoc.engine.execution.ScopedFixtureModel 5 | 6 | /** 7 | * A GroupFixtureModel is a representation of the glue code necessary to execute a [GroupFixture] 8 | * 9 | * @see GroupFixture 10 | * @see GroupExecution 11 | */ 12 | internal class GroupFixtureModel(val groupClass: Class<*>) : ScopedFixtureModel(groupClass) { 13 | 14 | /** 15 | * getFailFastExceptions returns the specified exceptions for fail fast if the annotation is set. 16 | * Otherwise it returns an empty list. 17 | * 18 | * @return a list of Exception classes that should be considered for fail fast 19 | */ 20 | val failFastExceptions: List> 21 | get() { 22 | return groupClass.getAnnotation(FailFast::class.java)?.onExceptionTypes?.map { it.java } ?: emptyList() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/execution/groups/ImplicitGroup.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.groups 2 | 3 | import org.livingdoc.api.documents.Group 4 | import org.livingdoc.engine.execution.documents.DocumentFixture 5 | 6 | /** 7 | * ImplicitGroup represents an implicit group assigned to [DocumentFixtures][DocumentFixture] without any other group. 8 | * 9 | * ImplicitGroup is used as the [GroupFixture] for these [DocumentFixtures][DocumentFixture] 10 | * 11 | * @see GroupFixture 12 | * @see DocumentFixture 13 | */ 14 | @Group 15 | internal class ImplicitGroup 16 | -------------------------------------------------------------------------------- /livingdoc-engine/src/main/kotlin/org/livingdoc/engine/fixtures/Fixture.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.fixtures 2 | 3 | import org.livingdoc.repositories.model.TestData 4 | import org.livingdoc.results.TestDataResult 5 | 6 | /** 7 | * This interface is the basis for all specialized fixture classes. 8 | * It wraps or represents a fixture and offers an execute function to execute it with some test data 9 | */ 10 | interface Fixture { 11 | /** 12 | * Executes the fixture class with the give testData 13 | * 14 | * @param testData A test data instance that can be mapped to the fixture 15 | * @return A TestDataResult for the execution 16 | */ 17 | fun execute(testData: T): TestDataResult 18 | } 19 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/java/org/livingdoc/engine/DecisionTableToFixtureMatcherTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine 2 | 3 | import org.junit.jupiter.api.Test 4 | 5 | internal class DecisionTableToFixtureMatcherTest { 6 | 7 | val cut = DecisionTableToFixtureMatcher() 8 | 9 | @Test 10 | fun `exception is thrown if document could not be found`() { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/java/org/livingdoc/engine/execution/documents/LifeCycleFixture.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.documents; 2 | 3 | import org.livingdoc.api.After; 4 | import org.livingdoc.api.Before; 5 | import org.livingdoc.api.documents.ExecutableDocument; 6 | import org.livingdoc.api.tagging.Tag; 7 | 8 | import static org.livingdoc.engine.MockkExtKt.clearJMockk; 9 | import static org.livingdoc.engine.MockkExtKt.mockkJClass; 10 | 11 | @Tag("test") 12 | @Tag("stub") 13 | @ExecutableDocument("") 14 | public class LifeCycleFixture { 15 | public static Callback callback = mockkJClass(Callback.class); 16 | 17 | @Before 18 | public static void before() { 19 | callback.before(); 20 | } 21 | 22 | @After 23 | public static void after() { 24 | callback.after(); 25 | } 26 | 27 | public static void reset() { 28 | clearJMockk(callback); 29 | } 30 | 31 | interface Callback { 32 | void before(); 33 | void after(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/java/org/livingdoc/engine/execution/examples/scenarios/LifeCycleFixture.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.examples.scenarios; 2 | 3 | import org.livingdoc.api.After; 4 | import org.livingdoc.api.Before; 5 | import org.livingdoc.api.fixtures.scenarios.Step; 6 | 7 | import static org.livingdoc.engine.MockkExtKt.clearJMockk; 8 | import static org.livingdoc.engine.MockkExtKt.mockkJClass; 9 | 10 | 11 | public class LifeCycleFixture { 12 | 13 | public static Callback callback = mockkJClass(Callback.class); 14 | 15 | @Before 16 | void before() { 17 | callback.before(); 18 | } 19 | 20 | @Step("step1") 21 | void step1() { 22 | callback.step1(); 23 | } 24 | 25 | @Step("step2") 26 | @Step("Alternate template for step2") 27 | void step2() { 28 | callback.step2(); 29 | } 30 | 31 | @After 32 | void after() { 33 | callback.after(); 34 | } 35 | 36 | public static void reset() { 37 | clearJMockk(callback); 38 | } 39 | 40 | public interface Callback { 41 | 42 | void before(); 43 | 44 | void step1(); 45 | 46 | void step2(); 47 | 48 | void after(); 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/java/org/livingdoc/engine/execution/groups/LifeCycleFixture.java: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.groups; 2 | 3 | import org.livingdoc.api.After; 4 | import org.livingdoc.api.Before; 5 | import org.livingdoc.api.documents.Group; 6 | 7 | import static org.livingdoc.engine.MockkExtKt.clearJMockk; 8 | import static org.livingdoc.engine.MockkExtKt.mockkJClass; 9 | 10 | @Group 11 | public class LifeCycleFixture { 12 | public static Callback callback = mockkJClass(Callback.class); 13 | 14 | @Before 15 | public static void before() { 16 | callback.before(); 17 | } 18 | 19 | @After 20 | public static void after() { 21 | callback.after(); 22 | } 23 | 24 | public static void reset() { 25 | clearJMockk(callback); 26 | } 27 | 28 | interface Callback { 29 | void before(); 30 | void after(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/MockkExt.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine 2 | 3 | import io.mockk.clearMocks 4 | import io.mockk.mockkClass 5 | 6 | fun clearJMockk(mock: Any) = clearMocks(mock, mock) 7 | 8 | fun mockkJClass(type: Class): T { 9 | return mockkClass(type.kotlin, relaxed = true) 10 | } 11 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/execution/documents/DocumentExecutionTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.documents 2 | 3 | import io.mockk.mockk 4 | import io.mockk.verifySequence 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.junit.jupiter.api.BeforeEach 7 | import org.junit.jupiter.api.Test 8 | 9 | class DocumentExecutionTest { 10 | @BeforeEach 11 | fun resetFixtures() { 12 | LifeCycleFixture.reset() 13 | } 14 | 15 | @Test 16 | fun `test life cycle of simple document`() { 17 | DocumentExecution( 18 | LifeCycleFixture::class.java, 19 | mockk(relaxed = true), 20 | mockk(relaxed = true), 21 | mockk(relaxed = true), 22 | mockk(relaxed = true) 23 | ).execute() 24 | 25 | val fixture = LifeCycleFixture.callback 26 | verifySequence { 27 | fixture.before() 28 | fixture.after() 29 | } 30 | } 31 | 32 | @Test 33 | fun `test tags in document result`() { 34 | val result = DocumentExecution( 35 | LifeCycleFixture::class.java, 36 | mockk(relaxed = true), 37 | mockk(relaxed = true), 38 | mockk(relaxed = true), 39 | mockk(relaxed = true) 40 | ).execute() 41 | 42 | assertThat(result.tags).contains("test", "stub") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/execution/examples/decisiontables/DecisionTableNoFixtureTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.examples.decisiontables 2 | 3 | import org.assertj.core.api.Assertions 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.repositories.model.TestDataDescription 6 | import org.livingdoc.repositories.model.decisiontable.DecisionTable 7 | import org.livingdoc.repositories.model.decisiontable.Field 8 | import org.livingdoc.repositories.model.decisiontable.Header 9 | import org.livingdoc.repositories.model.decisiontable.Row 10 | import org.livingdoc.results.Status 11 | 12 | internal class DecisionTableNoFixtureTest { 13 | 14 | @Test 15 | fun manualDecisionTableExecute() { 16 | val input = Header("input") 17 | val check = Header("check") 18 | val headers = arrayListOf(input, check) 19 | 20 | val row1 = Row(mapOf(input to Field("r1i"), check to Field("r1c"))) 21 | val row2 = Row(mapOf(input to Field("r2i"), check to Field("r2c"))) 22 | val rows = arrayListOf(row1, row2) 23 | 24 | val decisionTableMock = DecisionTable( 25 | headers, 26 | rows, 27 | TestDataDescription("MANUAL Test1", true, "") 28 | ) 29 | 30 | val cut = DecisionTableNoFixture() 31 | val result = cut.execute(decisionTableMock).status 32 | 33 | Assertions.assertThat(result).isInstanceOf(Status.Manual::class.java) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/execution/examples/scenarios/ScenarioNoFixtureExecutionTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.examples.scenarios 2 | 3 | import org.assertj.core.api.Assertions 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.repositories.model.TestDataDescription 6 | import org.livingdoc.repositories.model.scenario.Scenario 7 | import org.livingdoc.repositories.model.scenario.Step 8 | import org.livingdoc.results.Status 9 | 10 | internal class ScenarioNoFixtureExecutionTest { 11 | 12 | @Test 13 | fun execute() { 14 | val step1 = Step("when the customer scans a banana for 49 cents") 15 | val step2 = Step("and an apple for 39 cents") 16 | val step3 = Step("when the customer checks out, the total sum is 88") 17 | val steps = listOf(step1, step2, step3) 18 | 19 | val scenarioMock = Scenario( 20 | steps, 21 | TestDataDescription("MANUAL Test1", true, "") 22 | ) 23 | 24 | val cut = ScenarioNoFixture() 25 | val result = cut.execute(scenarioMock).status 26 | Assertions.assertThat(result).isInstanceOf(Status.Manual::class.java) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/execution/groups/GroupExecutionTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.execution.groups 2 | 3 | import io.mockk.mockk 4 | import io.mockk.verifySequence 5 | import org.junit.jupiter.api.BeforeEach 6 | import org.junit.jupiter.api.Test 7 | 8 | class GroupExecutionTest { 9 | @BeforeEach 10 | fun resetFixtures() { 11 | LifeCycleFixture.reset() 12 | } 13 | 14 | @Test 15 | fun `test life cycle of simple group`() { 16 | GroupExecution( 17 | LifeCycleFixture::class.java, 18 | listOf(), 19 | mockk(relaxed = true), 20 | mockk(relaxed = true), 21 | mockk(relaxed = true) 22 | ).execute() 23 | 24 | val fixture = LifeCycleFixture.callback 25 | verifySequence { 26 | fixture.before() 27 | fixture.after() 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/resources/DeclaringGroup.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.resources 2 | 3 | import org.livingdoc.api.disabled.Disabled 4 | import org.livingdoc.api.documents.ExecutableDocument 5 | import org.livingdoc.api.documents.Group 6 | 7 | @Group 8 | class DeclaringGroup { 9 | @Disabled 10 | @ExecutableDocument("", group = AnnotationGroup::class) 11 | class AmbiguousGroupExecutableDocument 12 | } 13 | 14 | @Group 15 | class AnnotationGroup 16 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/resources/DisabledDecisionTableDocument.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.resources 2 | 3 | import org.livingdoc.api.disabled.Disabled 4 | import org.livingdoc.api.documents.ExecutableDocument 5 | import org.livingdoc.api.fixtures.decisiontables.DecisionTableFixture 6 | 7 | @ExecutableDocument("local://Calculator.html") 8 | class DisabledDecisionTableDocument { 9 | 10 | @Disabled("Disabled DecisionTableFixture") 11 | @DecisionTableFixture(parallel = false) 12 | class DisabledDecisionTableFixture 13 | } 14 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/resources/DisabledExecutableDocument.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.resources 2 | 3 | import org.livingdoc.api.disabled.Disabled 4 | import org.livingdoc.api.documents.ExecutableDocument 5 | 6 | @Disabled("Skip this test document") 7 | @ExecutableDocument("local://Calculator.html") 8 | class DisabledExecutableDocument 9 | -------------------------------------------------------------------------------- /livingdoc-engine/src/test/kotlin/org/livingdoc/engine/resources/DisabledScenarioDocument.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.engine.resources 2 | 3 | import org.livingdoc.api.disabled.Disabled 4 | import org.livingdoc.api.documents.ExecutableDocument 5 | import org.livingdoc.api.fixtures.scenarios.ScenarioFixture 6 | 7 | @ExecutableDocument("local://Calculator.html") 8 | class DisabledScenarioDocument { 9 | 10 | @Disabled("Disabled ScenarioFixture") 11 | @ScenarioFixture 12 | class DisabledScenarioFixture 13 | } 14 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/livingdoc-extensions-api.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/extension/Extension.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.extension 2 | 3 | import kotlin.reflect.KClass 4 | 5 | annotation class Extension( 6 | val extensionClass: KClass<*> 7 | ) 8 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/reports/spi/Format.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.spi 2 | 3 | /** 4 | * Specifies the format of a [ReportRenderer], by the unique identifier [value]. 5 | * This annotation can only be used on [ReportRenderer]. 6 | */ 7 | @Target(AnnotationTarget.CLASS) 8 | @Retention(AnnotationRetention.RUNTIME) 9 | @MustBeDocumented 10 | annotation class Format(val value: String) 11 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/reports/spi/ReportRenderer.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.spi 2 | 3 | import org.livingdoc.results.documents.DocumentResult 4 | 5 | /** 6 | * The Service Provider Interface that must be implemented by all report renderers. Every [ReportRenderer] must also be 7 | * annotated with [Format] to specify the unique format of report which is generated by the [ReportRenderer]. 8 | */ 9 | interface ReportRenderer { 10 | /** 11 | * Renders the reports for all [documentResults][DocumentResult] with the specified [config]. This function must 12 | * also store the generated reports in the report specific location which can be configured with the [config] 13 | * parameter. 14 | */ 15 | fun render(documentResults: List, config: Map) 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/repositories/Document.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories 2 | 3 | import org.livingdoc.repositories.model.TestData 4 | 5 | /** 6 | * Document represents the [TestData] contained in a document fetched from a [DocumentRepository]. 7 | * 8 | * @see DocumentRepository 9 | * @see TestData 10 | */ 11 | open class Document(val elements: List) 12 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/repositories/DocumentFormat.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories 2 | 3 | import java.io.InputStream 4 | 5 | /** 6 | * A DocumentFormat describes the format in which data is stored inside a [DocumentRepository]. 7 | * 8 | * @see DocumentRepository 9 | */ 10 | interface DocumentFormat { 11 | /** 12 | * canHandle checks whether this DocumentFormat can handle a file with the specified extension. 13 | * 14 | * @param fileExtension the extension of the potential input file 15 | * @return true, if this DocumentFormat can handle the 16 | */ 17 | fun canHandle(fileExtension: String): Boolean 18 | 19 | /** 20 | * parse creates a [Document] from the input 21 | * 22 | * @param stream an [InputStream] representing the document to parse 23 | * @returns the parsed [Document] 24 | */ 25 | fun parse(stream: InputStream): Document 26 | } 27 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/repositories/DocumentNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories 2 | 3 | /** 4 | * A DocumentNotFoundException is thrown when a [DocumentRepository] cannot find an executable document. 5 | * 6 | * @see DocumentRepository 7 | */ 8 | open class DocumentNotFoundException(msg: String, cause: Throwable? = null) : RuntimeException(msg, cause) 9 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/repositories/DocumentRepository.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories 2 | 3 | /** 4 | * A [DocumentRepository] can look up [Documents][Document] from some storage location. 5 | * 6 | * @see Document 7 | */ 8 | interface DocumentRepository { 9 | 10 | /** 11 | * 12 | * @param documentIdentifier the identifier for the [Document] 13 | * @return the selected [Document] 14 | * @throws DocumentNotFoundException if documentIdentifier cannot be found in the repository 15 | */ 16 | fun getDocument(documentIdentifier: String): Document 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/repositories/DocumentRepositoryFactory.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories 2 | 3 | /** 4 | * A DocumentRepositoryFactory creates [DocumentRepository] instances from a name and configuration data. 5 | * 6 | * @see DocumentRepository 7 | */ 8 | interface DocumentRepositoryFactory { 9 | /** 10 | * createRepository creates an instance of [DocumentRepository] with a given name and the given configuration data. 11 | * 12 | * @param name the name of the [DocumentRepository] instance to create 13 | * @param configData configurationData for the [DocumentRepository] instance to create 14 | * @returns an instance of [DocumentRepository] 15 | */ 16 | fun createRepository(name: String, configData: Map): T 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/repositories/model/TestData.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.model 2 | 3 | import org.livingdoc.repositories.Document 4 | 5 | /** 6 | * TestData models a single example contained inside a [Document] 7 | * 8 | * @see Document 9 | */ 10 | interface TestData { 11 | val description: TestDataDescription 12 | } 13 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/repositories/model/TestDataDescription.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.model 2 | 3 | /** 4 | * Stores additional data for each TestData instance 5 | */ 6 | data class TestDataDescription( 7 | val name: String?, 8 | val isManual: Boolean, 9 | val descriptiveText: String 10 | ) 11 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/results/Status.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.results 2 | 3 | sealed class Status { 4 | 5 | /** Nothing is known about the result state. */ 6 | object Unknown : Status() 7 | 8 | /** The fixture was disabled and is ignored */ 9 | data class Disabled(val reason: String = "") : Status() 10 | 11 | /** The fixture will be tested manually */ 12 | object Manual : Status() 13 | 14 | /** Execution was skipped. */ 15 | object Skipped : Status() 16 | 17 | /** Successfully executed. */ 18 | object Executed : Status() 19 | 20 | /** A validation failed with an assertion error. */ 21 | data class Failed(val reason: AssertionError) : Status() 22 | 23 | /** The actual result is reported but not compared to an expected result */ 24 | data class ReportActualResult(val actualResult: String = "") : Status() 25 | 26 | /** An unexpected exception occurred. */ 27 | data class Exception(val exception: Throwable) : Status() 28 | } 29 | -------------------------------------------------------------------------------- /livingdoc-extensions-api/src/main/kotlin/org/livingdoc/results/TestDataResult.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.results 2 | 3 | /** 4 | * Base interface for all results. 5 | */ 6 | interface TestDataResult { 7 | /** 8 | * The [Status] of the [TestDataResult]. 9 | * 10 | * @see [Status] 11 | */ 12 | val status: Status 13 | } 14 | -------------------------------------------------------------------------------- /livingdoc-format-gherkin/livingdoc-format-gherkin.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | dependencies { 5 | implementation("io.cucumber:gherkin:${Versions.gherkin}") 6 | implementation("com.beust:klaxon:${Versions.klaxon}") 7 | 8 | implementation(project(":livingdoc-extensions-api")) 9 | implementation(project(":livingdoc-results")) 10 | implementation(project(":livingdoc-testdata")) 11 | 12 | testCompile("org.assertj:assertj-core:${Versions.assertJ}") 13 | } 14 | -------------------------------------------------------------------------------- /livingdoc-format-gherkin/src/main/kotlin/org/livingdoc/repositories/format/DataTable.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.format 2 | 3 | data class DataTable( 4 | val rows: List> 5 | ) 6 | -------------------------------------------------------------------------------- /livingdoc-format-gherkin/src/main/resources/META-INF/services/org.livingdoc.repositories.DocumentFormat: -------------------------------------------------------------------------------- 1 | org.livingdoc.repositories.format.GherkinFormat -------------------------------------------------------------------------------- /livingdoc-junit-engine/livingdoc-junit-engine.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | dependencies { 6 | implementation(project(":livingdoc-api")) 7 | implementation(project(":livingdoc-extensions-api")) 8 | implementation(project(":livingdoc-engine")) 9 | implementation(project(":livingdoc-repositories")) 10 | implementation(project(":livingdoc-results")) 11 | implementation(project(":livingdoc-testdata")) 12 | 13 | implementation(kotlin("reflect", Versions.kotlinVersion)) 14 | 15 | api("org.junit.platform:junit-platform-engine:${Versions.junitPlatform}") 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/LivingDocContext.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.junit.engine 2 | 3 | import org.junit.platform.engine.support.hierarchical.EngineExecutionContext 4 | import org.livingdoc.engine.LivingDoc 5 | 6 | class LivingDocContext : EngineExecutionContext { 7 | val livingDoc = LivingDoc() 8 | } 9 | -------------------------------------------------------------------------------- /livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/descriptors/EngineDescriptor.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.junit.engine.descriptors 2 | 3 | import org.junit.platform.engine.TestDescriptor 4 | import org.junit.platform.engine.UniqueId 5 | import org.junit.platform.engine.support.descriptor.EngineDescriptor 6 | import org.junit.platform.engine.support.hierarchical.Node 7 | import org.livingdoc.junit.engine.LivingDocContext 8 | 9 | class EngineDescriptor(uniqueId: UniqueId, private val documentClasses: List> = listOf()) : 10 | EngineDescriptor(uniqueId, "LivingDoc"), Node { 11 | override fun getType() = TestDescriptor.Type.CONTAINER 12 | 13 | override fun mayRegisterTests() = true 14 | 15 | override fun execute(context: LivingDocContext, dynamicTestExecutor: Node.DynamicTestExecutor): LivingDocContext { 16 | context.livingDoc.execute(documentClasses).forEach { documentResult -> 17 | val documentDescriptor = ExecutableDocumentDescriptor( 18 | uniqueId(uniqueId, documentResult.documentClass), 19 | documentResult 20 | ) 21 | 22 | dynamicTestExecutor.execute(documentDescriptor) 23 | } 24 | 25 | return context 26 | } 27 | 28 | private fun uniqueId(uniqueId: UniqueId, documentClass: Class<*>): UniqueId { 29 | return uniqueId.append("documentClass", documentClass.name) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/discovery/ClassSelectorHandler.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.junit.engine.discovery 2 | 3 | import org.junit.platform.engine.discovery.ClassSelector 4 | 5 | class ClassSelectorHandler : SelectorHandler() { 6 | 7 | override fun selectDocumentClasses(selector: ClassSelector, classFilter: (Class<*>) -> Boolean): List> { 8 | val javaClass = selector.javaClass 9 | if (classFilter.invoke(javaClass)) { 10 | return listOf(javaClass) 11 | } 12 | // TODO: not qualified == error? 13 | return listOf() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/discovery/ClasspathRootSelectorHandler.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.junit.engine.discovery 2 | 3 | import org.junit.platform.commons.util.ReflectionUtils.findAllClassesInClasspathRoot 4 | import org.junit.platform.engine.discovery.ClasspathRootSelector 5 | 6 | class ClasspathRootSelectorHandler : SelectorHandler() { 7 | 8 | private val everyNameIsOk = { _: String -> true } 9 | 10 | override fun selectDocumentClasses(selector: ClasspathRootSelector, classFilter: (Class<*>) -> Boolean): 11 | List> { 12 | return findAllClassesInClasspathRoot(selector.classpathRoot, classFilter, everyNameIsOk) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/discovery/PackageSelectorHandler.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.junit.engine.discovery 2 | 3 | import org.junit.platform.commons.util.ReflectionUtils.findAllClassesInPackage 4 | import org.junit.platform.engine.discovery.PackageSelector 5 | 6 | class PackageSelectorHandler : SelectorHandler() { 7 | 8 | private val everyNameIsOk = { _: String -> true } 9 | 10 | override fun selectDocumentClasses(selector: PackageSelector, classFilter: (Class<*>) -> Boolean): List> { 11 | return findAllClassesInPackage(selector.packageName, classFilter, everyNameIsOk) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /livingdoc-junit-engine/src/main/kotlin/org/livingdoc/junit/engine/discovery/SelectorHandler.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.junit.engine.discovery 2 | 3 | import org.junit.platform.engine.DiscoverySelector 4 | import org.livingdoc.api.documents.ExecutableDocument 5 | import org.livingdoc.api.documents.Group 6 | 7 | abstract class SelectorHandler { 8 | 9 | private val executableDocumentAnnotationIsPresent = { candidateClass: Class<*> -> 10 | candidateClass.isAnnotationPresent(ExecutableDocument::class.java) 11 | } 12 | 13 | private val groupAnnotationIsPresent = { candidateClass: Class<*> -> 14 | candidateClass.isAnnotationPresent(Group::class.java) 15 | } 16 | 17 | /** 18 | * Select all Document classes 19 | */ 20 | fun selectDocumentClasses(selector: T): List> { 21 | return selectDocumentClasses(selector, executableDocumentAnnotationIsPresent) 22 | } 23 | 24 | /** 25 | * Select all Group classes 26 | */ 27 | fun selectGroupClasses(selector: T): List> { 28 | return selectDocumentClasses(selector, groupAnnotationIsPresent) 29 | } 30 | 31 | protected abstract fun selectDocumentClasses(selector: T, classFilter: (Class<*>) -> Boolean): List> 32 | } 33 | -------------------------------------------------------------------------------- /livingdoc-junit-engine/src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine: -------------------------------------------------------------------------------- 1 | org.livingdoc.junit.engine.LivingDocTestEngine 2 | -------------------------------------------------------------------------------- /livingdoc-reports/livingdoc-reports.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | repositories { 6 | maven { 7 | url = uri("https://packages.atlassian.com/mvn/maven-external") 8 | } 9 | } 10 | 11 | dependencies { 12 | implementation("org.codehaus.jackson:jackson-core-asl:1.9.13") 13 | implementation("org.codehaus.jackson:jackson-mapper-asl:1.9.13") 14 | implementation("org.codehaus.jackson:jackson-jaxrs:1.9.13") 15 | implementation("org.codehaus.jackson:jackson-xc:1.9.13") 16 | implementation("jakarta.xml.bind:jakarta.xml.bind-api:2.3.2") 17 | runtimeOnly("org.glassfish.jaxb:jaxb-runtime:2.3.2") 18 | implementation("com.atlassian.confluence:confluence-rest-client:7.0.3") 19 | implementation("org.jsoup:jsoup:${Versions.jsoup}") 20 | implementation("com.beust:klaxon:${Versions.klaxon}") 21 | implementation(project(":livingdoc-config")) 22 | implementation(project(":livingdoc-api")) 23 | implementation(project(":livingdoc-extensions-api")) 24 | implementation(project(":livingdoc-results")) 25 | implementation(project(":livingdoc-repositories")) 26 | implementation(project(":livingdoc-testdata")) 27 | 28 | testImplementation("org.assertj:assertj-core:${Versions.assertJ}") 29 | } 30 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/ReportWriter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports 2 | 3 | import java.nio.file.Files 4 | import java.nio.file.Path 5 | import java.nio.file.Paths 6 | import java.nio.file.StandardOpenOption 7 | 8 | class ReportWriter( 9 | private val outputDir: String = REPORT_OUTPUT_PATH, 10 | private val fileExtension: String 11 | ) { 12 | 13 | private companion object { 14 | const val REPORT_OUTPUT_PATH = "build/livingdoc/reports/" 15 | const val REPORT_OUTPUT_FILENAME = "result" 16 | } 17 | 18 | /** 19 | * Write the [textToWrite] as report to the configured location. The reports filename will contain the [reportName] 20 | * and end with the [fileExtension]. 21 | */ 22 | fun writeToFile(textToWrite: String, reportName: String = REPORT_OUTPUT_FILENAME): Path { 23 | val path = Paths.get(outputDir) 24 | Files.createDirectories(path) 25 | 26 | val file = path.resolve("$reportName.$fileExtension") 27 | Files.write(file, textToWrite.toByteArray(), StandardOpenOption.CREATE_NEW) 28 | return file 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/config/ReportDefinition.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.config 2 | 3 | data class ReportDefinition( 4 | var name: String = "", 5 | var format: String = "", 6 | var config: Map = emptyMap() 7 | ) 8 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/config/ReportsConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.config 2 | 3 | import org.livingdoc.config.ConfigProvider 4 | 5 | data class ReportsConfig(var reports: List = emptyList()) { 6 | companion object { 7 | fun from(configProvider: ConfigProvider): ReportsConfig { 8 | return configProvider.getConfigAs("reports", ReportsConfig::class) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/attachment/ConfluenceAttachmentReportConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.attachment 2 | 3 | /** 4 | * The configuration object for the Confluence Report. 5 | * 6 | * @param repositoryName The name of the repository for which the report should be generated 7 | * @param filename The filename the uploaded report should have in confluence 8 | * @param baseURL The baseURL of the Confluence Server with the format `protocol://host:port` 9 | * @param path The Context path of the Confluence Server, for `/` 10 | * @param username The username of a confluence user with access to the Executable Documents. 11 | * @param password The password of the confluence user given by username. 12 | * @param comment A comment that is added to the attachment 13 | */ 14 | data class ConfluenceAttachmentReportConfig( 15 | var repositoryName: String = "", 16 | var filename: String = "report.html", 17 | var baseURL: String = "", 18 | var path: String = "", 19 | var username: String = "", 20 | var password: String = "", 21 | var comment: String = "" 22 | ) 23 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/tree/ConfluencePageTreeReportConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree 2 | 3 | data class ConfluencePageTreeReportConfig( 4 | var rootContentId: Long = 0, 5 | var baseURL: String = "", 6 | var path: String = "", 7 | var username: String = "", 8 | var password: String = "", 9 | var comment: String = "" 10 | ) 11 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/tree/elements/ConfluenceError.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree.elements 2 | 3 | import org.livingdoc.reports.html.elements.HtmlElement 4 | import java.io.PrintWriter 5 | import java.io.StringWriter 6 | 7 | /** 8 | * ConfluenceError displays information about an error that caused a failure in a test run. 9 | */ 10 | class ConfluenceError(error: Throwable) : HtmlElement("ac:structured-macro") { 11 | init { 12 | attr("ac:name", "warning") 13 | 14 | error.message.takeUnless { it.isNullOrBlank() }?.let { message -> 15 | child { 16 | HtmlElement("ac:parameter") { 17 | attr("ac:name", "ac:title") 18 | 19 | text { message } 20 | } 21 | } 22 | } 23 | 24 | child { 25 | HtmlElement("ac:rich-text-body") { 26 | child { 27 | HtmlElement("pre") { 28 | text { 29 | StringWriter().use { writer -> 30 | error.printStackTrace(PrintWriter(writer)) 31 | 32 | writer.toString() 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/tree/elements/ConfluenceIndex.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree.elements 2 | 3 | import org.livingdoc.reports.html.elements.HtmlTable 4 | import org.livingdoc.reports.html.elements.summaryTableHeader 5 | import org.livingdoc.results.documents.DocumentResult 6 | 7 | /** 8 | * ConfluenceIndex is a [ConfluencePage] containing a summary about a test run. 9 | */ 10 | class ConfluenceIndex(reports: List) : ConfluencePage() { 11 | init { 12 | val reportsByTag = reports.flatMap { report -> 13 | listOf( 14 | listOf("all" to report), 15 | report.tags.map { tag -> 16 | tag to report 17 | } 18 | ).flatten() 19 | }.groupBy({ it.first }, { it.second }) 20 | 21 | child { 22 | HtmlTable { 23 | summaryTableHeader() 24 | 25 | reportsByTag.map { (tag, documentResults) -> 26 | cfTagRow(tag, documentResults) 27 | cfReportRow(documentResults) 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/tree/elements/ConfluenceLink.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree.elements 2 | 3 | import org.livingdoc.reports.html.MILLISECONDS_DIVIDER 4 | import org.livingdoc.reports.html.elements.HtmlElement 5 | import org.livingdoc.results.documents.DocumentResult 6 | 7 | /** 8 | * A link element in a Confluence page tree report 9 | * 10 | * @param documentResult the result of the document to link to 11 | */ 12 | class ConfluenceLink(documentResult: DocumentResult) : HtmlElement("ac:link") { 13 | init { 14 | child { 15 | HtmlElement("ri:page") { 16 | attr("ri:content-title", documentResult.documentClass.name) 17 | } 18 | } 19 | 20 | child { 21 | HtmlElement("ac:link-body") { 22 | child { 23 | HtmlElement("span") { 24 | attr("style", determineCfStylesForStatus(documentResult.documentStatus)) 25 | 26 | text { 27 | documentResult.documentClass.name + 28 | " (%.3fs)".format(documentResult.time.toMillis() / MILLISECONDS_DIVIDER) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/tree/elements/ConfluencePage.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree.elements 2 | 3 | import org.jsoup.nodes.Document 4 | import org.livingdoc.reports.html.elements.HtmlElement 5 | 6 | /** 7 | * ConfluencePage represents a content body in the Confluence XHTML storage format. 8 | */ 9 | open class ConfluencePage : HtmlElement("div") { 10 | init { 11 | Document("").apply { 12 | outputSettings().prettyPrint(false) 13 | }.appendChild(element) 14 | } 15 | 16 | override fun toString(): String { 17 | return element.html() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/tree/elements/ConfluenceStatus.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree.elements 2 | 3 | import org.livingdoc.reports.html.elements.HtmlElement 4 | 5 | /** 6 | * A ConfluenceStatus represents a single tag in a [ConfluenceStatusBar] 7 | */ 8 | class ConfluenceStatus(tag: String) : HtmlElement("ac:structured-macro") { 9 | init { 10 | attr("ac:name", "status") 11 | attr("ac:schema-version", "1") 12 | 13 | child { 14 | HtmlElement("ac:parameter") { 15 | attr("ac:name", "colour") 16 | 17 | text { "Blue" } 18 | } 19 | } 20 | 21 | child { 22 | HtmlElement("ac:parameter") { 23 | attr("ac:name", "title") 24 | 25 | text { tag } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/tree/elements/ConfluenceStatusBar.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree.elements 2 | 3 | import org.livingdoc.reports.html.elements.HtmlElement 4 | import org.livingdoc.results.documents.DocumentResult 5 | 6 | /** 7 | * A ConfluenceStatusBar lists all tags of a [DocumentResult] in a [ConfluenceReport] 8 | */ 9 | class ConfluenceStatusBar(tags: List) : HtmlElement("h2") { 10 | init { 11 | tags.forEach { tag -> 12 | child { 13 | ConfluenceStatus(tag) 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/confluence/tree/elements/HtmlList.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree.elements 2 | 3 | import org.livingdoc.reports.html.elements.HtmlElement 4 | import org.livingdoc.reports.html.elements.HtmlList 5 | import org.livingdoc.results.documents.DocumentResult 6 | import org.livingdoc.results.examples.scenarios.StepResult 7 | 8 | /** 9 | * Fills a list with the given [scenario step results][StepResult] 10 | * 11 | * @param stepResults A list of [step results][StepResult] 12 | */ 13 | fun HtmlList.cfSteps(stepResults: List) { 14 | stepResults.forEach { (value, result) -> 15 | child { 16 | HtmlElement("li") { 17 | attr("style", determineCfStylesForStatus(result)) 18 | text { value } 19 | } 20 | } 21 | } 22 | } 23 | 24 | fun HtmlList.cfLinkList(reports: List) { 25 | reports.map { report -> 26 | child { 27 | HtmlElement("li") { 28 | child { 29 | ConfluenceLink(report) 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/html/HtmlReportConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.html 2 | 3 | data class HtmlReportConfig( 4 | var outputDir: String = "livingdoc/reports/html", 5 | var generateIndex: Boolean = false 6 | ) 7 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/html/ReportScript.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.html 2 | 3 | private const val SCRIPT_CONTENT = """ 4 | function collapse (indicator, row) { 5 | var indicatorElem = document.getElementById(indicator); 6 | var rowElem = document.getElementById(row); 7 | if (rowElem.classList.contains("hidden")) { 8 | indicatorElem.innerHTML = "⏷"; 9 | } else { 10 | indicatorElem.innerHTML = "⏵"; 11 | } 12 | rowElem.classList.toggle("hidden"); 13 | }""" 14 | 15 | fun reportScript(): String { 16 | return " ${SCRIPT_CONTENT.trimIndent()}" 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/html/elements/HtmlDescription.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.html.elements 2 | 3 | /** 4 | * A test description element in a HTML report 5 | * 6 | * @param block A lambda that creates the paragraphs of this description 7 | */ 8 | class HtmlDescription(block: HtmlDescription.() -> Unit) : HtmlElement("div") { 9 | init { 10 | block() 11 | } 12 | } 13 | 14 | /** 15 | * Adds the given paragraphs to the [HtmlDescription] element 16 | * 17 | * @param paragraphs A list of strings with each entry representing a paragraph 18 | */ 19 | fun HtmlDescription.paragraphs(paragraphs: List) { 20 | paragraphs.forEach { paragraph -> 21 | if (paragraph.isNotEmpty()) 22 | child { 23 | HtmlElement("p", paragraph) 24 | } 25 | } 26 | } 27 | /** 28 | * Adds a given list of Strings as one paragraph to the [HtmlDescription] element 29 | * 30 | * @param content A list of strings which are merged to a paragraph 31 | */ 32 | fun HtmlDescription.content(content: List) { 33 | child { HtmlElement("p", content.joinToString("")) } 34 | } 35 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/html/elements/HtmlErrors.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.html.elements 2 | 3 | class HtmlErrorContext { 4 | private var popupErrorNumber = 0 5 | val popupErrors = ArrayList() 6 | 7 | fun getNextErrorNumber() = ++popupErrorNumber 8 | fun addPopupError(htmlError: HtmlError) = popupErrors.add(htmlError) 9 | } 10 | 11 | class HtmlError(val number: Int, val message: String, val stacktrace: String) 12 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/html/elements/HtmlFooter.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.html.elements 2 | 3 | class HtmlFooter(content: HtmlFooter.() -> Unit) : HtmlElement("div") { 4 | 5 | init { 6 | cssClass("footer") 7 | content() 8 | } 9 | } 10 | 11 | /** 12 | * This creates the footer for the reports 13 | */ 14 | fun HtmlFooter.populateFooter() { 15 | child { 16 | HtmlElement("p") { 17 | child { 18 | HtmlElement("a") { 19 | text { 20 | "↩ Index" 21 | } 22 | attr("href", "index.html") 23 | } 24 | } 25 | } 26 | } 27 | child { 28 | HtmlElement("p") { 29 | text { "Generated with Living Doc 2." } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/html/elements/HtmlLink.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.html.elements 2 | 3 | import org.livingdoc.results.Status 4 | 5 | /** 6 | * A link element in a HTML report 7 | * 8 | * @param linkAddress The address the link is pointing to 9 | */ 10 | class HtmlLink(linkAddress: String) : 11 | HtmlElement("a") { 12 | 13 | init { 14 | attr("href", linkAddress) 15 | } 16 | 17 | /** 18 | * A link element in a HTML report 19 | * 20 | * @param linkAddress The address the link is pointing to 21 | * @param value The text displayed by the link 22 | */ 23 | constructor(linkAddress: String, value: String) : 24 | this(linkAddress) { 25 | text { value } 26 | } 27 | 28 | /** 29 | * A link element in a HTML report 30 | * 31 | * @param linkAddress The address the link is pointing to 32 | * @param block A lambda generating the content of this link 33 | */ 34 | constructor(linkAddress: String, block: HtmlLink.() -> Unit) : 35 | this(linkAddress) { 36 | block() 37 | } 38 | } 39 | 40 | /** 41 | * Generates a link that points to a test execution 42 | */ 43 | fun HtmlLink.resultLink(className: String, status: Status) { 44 | cssClass(determineCssClassForBackgroundColor(status)) 45 | text { className } 46 | } 47 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/html/elements/HtmlTitle.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.html.elements 2 | 3 | class HtmlTitle(value: String?) : HtmlElement("h2", value ?: "") 4 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/kotlin/org/livingdoc/reports/json/JsonReportConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.json 2 | 3 | data class JsonReportConfig(var outputDir: String = "livingdoc/reports/json", var prettyPrinted: Boolean = true) 4 | -------------------------------------------------------------------------------- /livingdoc-reports/src/main/resources/META-INF/services/org.livingdoc.reports.spi.ReportRenderer: -------------------------------------------------------------------------------- 1 | org.livingdoc.reports.html.HtmlReportRenderer 2 | org.livingdoc.reports.json.JsonReportRenderer 3 | org.livingdoc.reports.confluence.attachment.ConfluenceAttachmentReportRenderer 4 | org.livingdoc.reports.confluence.tree.ConfluencePageTreeReportRenderer 5 | -------------------------------------------------------------------------------- /livingdoc-reports/src/test/kotlin/org/livingdoc/reports/ReportsManagerTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.config.ConfigProvider 6 | import org.livingdoc.reports.config.ReportsConfig 7 | 8 | internal class ReportsManagerTest { 9 | 10 | @Test 11 | fun `parse config correctly`() { 12 | val configYaml = """ 13 | 14 | reports: 15 | - name: "default" 16 | format: "html" 17 | config: 18 | foo: "test repository one" 19 | bar: 1.11 20 | - name: "alt-1" 21 | format: "json" 22 | config: 23 | foo: "test repository two" 24 | bar: 2.22 25 | """.trimIndent() 26 | 27 | val configuration = readConfigFromString(configYaml) 28 | assertThat(configuration.reports).hasSize(2) 29 | } 30 | 31 | private fun readConfigFromString(config: String): ReportsConfig { 32 | return ReportsConfig.from(ConfigProvider.loadFromStream(config.byteInputStream())) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /livingdoc-reports/src/test/kotlin/org/livingdoc/reports/confluence/tree/ConfluencePageTreeReportConfigTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Assertions.* 5 | import org.junit.jupiter.api.Test 6 | import org.livingdoc.config.YamlUtils 7 | 8 | internal class ConfluencePageTreeReportConfigTest { 9 | @Test 10 | fun `can parse config`() { 11 | val parsedConfig = YamlUtils.toObject(mapOf( 12 | "rootContentId" to 27, 13 | "baseURL" to "https://internal.example.com/", 14 | "username" to "livingdoc-reports", 15 | "password" to "secure!p4ssw0rd", 16 | "path" to "/confluence", 17 | "comment" to "Jenkins from Staging" 18 | ), ConfluencePageTreeReportConfig::class) 19 | 20 | assertThat(parsedConfig).isEqualTo( 21 | ConfluencePageTreeReportConfig( 22 | 27, 23 | "https://internal.example.com/", 24 | "/confluence", 25 | "livingdoc-reports", 26 | "secure!p4ssw0rd", 27 | "Jenkins from Staging" 28 | ) 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /livingdoc-reports/src/test/kotlin/org/livingdoc/reports/confluence/tree/elements/ConfluenceLinkTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.reports.confluence.tree.elements 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.livingdoc.results.Status 6 | import org.livingdoc.results.documents.DocumentResult 7 | import java.time.Duration 8 | 9 | internal class ConfluenceLinkTest { 10 | @Test 11 | fun `check correct format of confluence link`() { 12 | val link = ConfluenceLink( 13 | DocumentResult.Builder() 14 | .withDocumentClass(ConfluenceLinkTest::class.java) 15 | .withStatus(Status.Executed) 16 | .withTime(Duration.ofMillis(176)) 17 | .withTags(emptyList()) 18 | .build() 19 | ).toString() 20 | 21 | assertThat(link).isEqualToIgnoringWhitespace( 22 | """ 23 | 24 | 25 | 26 | org.livingdoc.reports.confluence.tree.elements.ConfluenceLinkTest (${"%.3fs".format(0.176)}) 27 | 28 | 29 | """ 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /livingdoc-repositories/livingdoc-repositories.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | dependencies { 5 | implementation("org.slf4j:slf4j-api:${Versions.slf4j}") 6 | implementation("org.jsoup:jsoup:${Versions.jsoup}") 7 | implementation("com.vladsch.flexmark:flexmark:${Versions.flexmark}") 8 | implementation("com.vladsch.flexmark:flexmark-ext-tables:${Versions.flexmark}") 9 | 10 | implementation(project(":livingdoc-config")) 11 | implementation(project(":livingdoc-extensions-api")) 12 | implementation(project(":livingdoc-results")) 13 | implementation(project(":livingdoc-testdata")) 14 | implementation(project(":livingdoc-format-gherkin")) 15 | 16 | testCompile("ch.qos.logback:logback-classic:${Versions.logback}") 17 | testCompile("org.assertj:assertj-core:${Versions.assertJ}") 18 | 19 | testImplementation("io.cucumber:gherkin:${Versions.gherkin}") 20 | testImplementation("com.github.tomakehurst:wiremock-jre8:${Versions.wiremock}") 21 | } 22 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/ParseException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories 2 | 3 | /** 4 | * A ParseException is thrown when a [Document] can not be parsed 5 | */ 6 | class ParseException(message: String) : RuntimeException(message) 7 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/cache/CacheConfiguration.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.cache 2 | 3 | /** 4 | * Contains the configuration for caching documents. 5 | * 6 | * Repositories that want to use the caching can use this to configure their 7 | * configuration. 8 | */ 9 | class CacheConfiguration( 10 | var path: String = "build/livingdoc/cache", 11 | var cachePolicy: String = CacheHelper.CACHE_ALWAYS 12 | ) 13 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/cache/InvalidCachePolicyException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.cache 2 | 3 | /** 4 | * InvalidCachePolicyException is thrown when the policy statement in 5 | * the cache configuration does not have a valid value. 6 | */ 7 | class InvalidCachePolicyException(policy: String) : 8 | RuntimeException("Invalid policy statement in configuration: $policy") 9 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/config/RepositoryConfiguration.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.config 2 | 3 | import org.livingdoc.config.ConfigProvider 4 | 5 | /** 6 | * The RepositoryConfiguration contains all RepositoryDefinition used by this run of LivingDoc. 7 | */ 8 | data class RepositoryConfiguration( 9 | var repositories: List = emptyList() 10 | ) { 11 | companion object { 12 | fun from(configProvider: ConfigProvider): RepositoryConfiguration { 13 | return configProvider.getConfigAs("repositories", RepositoryConfiguration::class) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/config/RepositoryDefinition.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.config 2 | 3 | import org.livingdoc.repositories.DocumentRepository 4 | 5 | /** 6 | * A RepositoryDefinition describes a single [DocumentRepository] inside a [RepositoryConfiguration]. 7 | * 8 | * @see DocumentRepository 9 | * @see RepositoryConfiguration 10 | */ 11 | data class RepositoryDefinition( 12 | var name: String = "default", 13 | var factory: String? = null, 14 | var config: Map = emptyMap() 15 | ) 16 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/format/DocumentFormatManager.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.format 2 | 3 | import org.livingdoc.repositories.DocumentFormat 4 | import java.util.* 5 | 6 | /** 7 | * A Manager for Document Formats. The Manager uses the ServiceLoader API to find the Formats. 8 | */ 9 | object DocumentFormatManager { 10 | 11 | private val documentFormats: List = ServiceLoader.load(DocumentFormat::class.java).toList() 12 | 13 | /** 14 | * Get the Format for a file extension 15 | * 16 | * @throws DocumentFormatNotFoundException 17 | */ 18 | fun getFormat(extension: String): DocumentFormat { 19 | return documentFormats 20 | .firstOrNull { it.canHandle(extension) } 21 | ?: throw DocumentFormatNotFoundException(extension) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/format/DocumentFormatNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.format 2 | 3 | import org.livingdoc.repositories.DocumentFormat 4 | 5 | /** 6 | * DocumentFormatNotFoundException is thrown when the [DocumentFormatManager] can not find a [DocumentFormat] for a 7 | * given file extension. 8 | * 9 | * @see DocumentFormat 10 | * @see DocumentFormatManager 11 | */ 12 | class DocumentFormatNotFoundException(fileExtension: String) : 13 | RuntimeException(fileExtension) 14 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/format/HtmlDocument.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.format 2 | 3 | import org.livingdoc.repositories.Document 4 | import org.livingdoc.repositories.model.TestData 5 | 6 | /** 7 | * A HtmlDocument is a [Document] in HTML format. 8 | * 9 | * It contains the parsed representation of the HTML DOM tree. 10 | * @see Document 11 | */ 12 | class HtmlDocument( 13 | elements: List 14 | ) : Document(elements) 15 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/kotlin/org/livingdoc/repositories/format/ParseContext.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.format 2 | 3 | /** 4 | * A context class used during parsing of documents 5 | * It helps mapping a headline to all following test cases 6 | */ 7 | data class ParseContext( 8 | val headline: String? = null, 9 | val descriptiveText: String = "" 10 | ) { 11 | /** 12 | * Checks whether the current [ParseContext] indicates a manual test 13 | */ 14 | fun isManual(): Boolean { 15 | return headline?.contains("MANUAL") ?: false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/main/resources/META-INF/services/org.livingdoc.repositories.DocumentFormat: -------------------------------------------------------------------------------- 1 | org.livingdoc.repositories.format.HtmlFormat 2 | org.livingdoc.repositories.format.MarkdownFormat -------------------------------------------------------------------------------- /livingdoc-repositories/src/test/kotlin/org/livingdoc/repositories/format/DemoDocumentFormat.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.format 2 | 3 | import org.livingdoc.repositories.Document 4 | import org.livingdoc.repositories.DocumentFormat 5 | import java.io.InputStream 6 | 7 | class DemoDocumentFormat : DocumentFormat { 8 | private val supportedFileExtensions = setOf("df", "dfm") 9 | 10 | override fun canHandle(fileExtension: String): Boolean { 11 | return supportedFileExtensions.contains(fileExtension.toLowerCase()) 12 | } 13 | 14 | override fun parse(stream: InputStream): Document { 15 | return Document(emptyList()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/test/kotlin/org/livingdoc/repositories/format/DocumentFormatManagerTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.format 2 | 3 | import org.assertj.core.api.Assertions.* 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.api.assertThrows 6 | import org.junit.jupiter.params.ParameterizedTest 7 | import org.junit.jupiter.params.provider.ValueSource 8 | import org.livingdoc.repositories.format.DocumentFormatManager.getFormat 9 | 10 | internal class DocumentFormatManagerTest { 11 | 12 | @ValueSource(strings = ["df", "dfm"]) 13 | @ParameterizedTest fun `following file types are supported`(fileExtension: String) { 14 | assertThat(getFormat(fileExtension)).isNotNull() 15 | } 16 | 17 | @Test fun `exception is thrown on getting unknown format`() { 18 | assertThrows { 19 | getFormat("xml") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /livingdoc-repositories/src/test/resources/META-INF/services/org.livingdoc.repositories.DocumentFormat: -------------------------------------------------------------------------------- 1 | org.livingdoc.repositories.format.DemoDocumentFormat -------------------------------------------------------------------------------- /livingdoc-repository-confluence/livingdoc-repository-confluence.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | repositories { 6 | maven { 7 | url = uri("https://packages.atlassian.com/mvn/maven-external") 8 | } 9 | } 10 | 11 | dependencies { 12 | implementation("org.slf4j:slf4j-api:${Versions.slf4j}") 13 | implementation("com.atlassian.confluence:confluence-rest-client:7.0.3") 14 | implementation("jakarta.xml.bind:jakarta.xml.bind-api:2.3.2") 15 | runtimeOnly("org.glassfish.jaxb:jaxb-runtime:2.3.2") 16 | implementation(project(":livingdoc-config")) 17 | implementation(project(":livingdoc-extensions-api")) 18 | implementation(project(":livingdoc-repositories")) 19 | 20 | testImplementation("ch.qos.logback:logback-classic:${Versions.logback}") 21 | testImplementation("com.github.tomakehurst:wiremock-jre8:${Versions.wiremock}") 22 | testImplementation("org.assertj:assertj-core:${Versions.assertJ}") 23 | } 24 | -------------------------------------------------------------------------------- /livingdoc-repository-confluence/src/main/kotlin/org/livingdoc/repositories/confluence/ConfluenceDocumentNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.confluence 2 | 3 | import org.livingdoc.repositories.DocumentNotFoundException 4 | 5 | class ConfluenceDocumentNotFoundException : DocumentNotFoundException { 6 | constructor(id: String, url: String) : super("Could not find document with ID [$id] on server [$url]!") 7 | constructor( 8 | throwable: Throwable, 9 | id: String, 10 | url: String 11 | ) : super("Could not find document with ID [$id] on server [$url]!", throwable) 12 | constructor(parts: Int) : super("Found to many version identifiers! Found $parts but only 1 is allowed!") 13 | } 14 | -------------------------------------------------------------------------------- /livingdoc-repository-confluence/src/main/kotlin/org/livingdoc/repositories/confluence/ConfluenceRepositoryConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.confluence 2 | 3 | import org.livingdoc.repositories.cache.CacheConfiguration 4 | 5 | /** 6 | * The configuration object for the Confluence Repository. 7 | * 8 | * @param baseURL The baseURL of the Confluence Server with the format `protocol://host:port` 9 | * @param path The Context path of the Confluence Server, for `/` 10 | * @param username The username of a confluence user with access to the Executable Documents. 11 | * @param password The password of the confluence user given by username. 12 | */ 13 | class ConfluenceRepositoryConfig( 14 | var baseURL: String = "", 15 | var path: String = "", 16 | var username: String = "", 17 | var password: String = "", 18 | var cacheConfig: CacheConfiguration = CacheConfiguration() 19 | ) 20 | -------------------------------------------------------------------------------- /livingdoc-repository-confluence/src/main/kotlin/org/livingdoc/repositories/confluence/ConfluenceRepositoryFactory.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.confluence 2 | 3 | import org.livingdoc.config.YamlUtils 4 | import org.livingdoc.repositories.DocumentRepositoryFactory 5 | 6 | /** 7 | * This Factory is used to create a ConfluenceRepository. This Factory must be specified in the livingdoc.yml to use the 8 | * Confluence Repository. For more details about the {@see ConfluenceRepository} see it's documentation. 9 | */ 10 | class ConfluenceRepositoryFactory : DocumentRepositoryFactory { 11 | override fun createRepository(name: String, configData: Map): ConfluenceRepository { 12 | val config = YamlUtils.toObject(configData, ConfluenceRepositoryConfig::class) 13 | return ConfluenceRepository(name, config) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /livingdoc-repository-confluence/src/test/kotlin/org/livingdoc/repositories/confluence/ConfluenceRepositoryFactoryTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.confluence 2 | 3 | import org.junit.jupiter.api.Assertions.* 4 | import org.junit.jupiter.api.Test 5 | 6 | internal class ConfluenceRepositoryFactoryTest { 7 | 8 | private val sut: ConfluenceRepositoryFactory = ConfluenceRepositoryFactory() 9 | 10 | @Test 11 | fun testCreateRepositoryEmptyConfig() { 12 | assertDoesNotThrow { sut.createRepository("someName", emptyMap()) } 13 | } 14 | 15 | @Test 16 | fun testCreateRepositoryWithConfig() { 17 | val config = mapOf( 18 | "baseURL" to "http://confluence.example.com", 19 | "path" to "/", 20 | "username" to "livingdoc", 21 | "password" to "very good password" 22 | ) 23 | assertDoesNotThrow { sut.createRepository("someName", config) } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /livingdoc-repository-file/livingdoc-repository-file.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | dependencies { 6 | implementation("org.slf4j:slf4j-api:${Versions.slf4j}") 7 | implementation("org.jsoup:jsoup:${Versions.jsoup}") 8 | implementation(project(":livingdoc-config")) 9 | implementation(project(":livingdoc-extensions-api")) 10 | implementation(project(":livingdoc-repositories")) 11 | 12 | testRuntimeOnly(project(":livingdoc-junit-engine")) 13 | 14 | testImplementation("ch.qos.logback:logback-classic:${Versions.logback}") 15 | testImplementation("org.assertj:assertj-core:${Versions.assertJ}") 16 | testImplementation(project(":livingdoc-api")) 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/file/DocumentFile.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.file 2 | 3 | import java.io.File 4 | import org.livingdoc.repositories.Document 5 | 6 | /** 7 | * A DocumentFile wraps a [File] that contains a [Document] 8 | * 9 | * @see Document 10 | * @see File 11 | */ 12 | open class DocumentFile(private val file: File) { 13 | open fun extension() = file.extension 14 | open fun stream() = file.inputStream() 15 | } 16 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/file/FileDocumentNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.file 2 | 3 | import org.livingdoc.repositories.Document 4 | import org.livingdoc.repositories.DocumentNotFoundException 5 | import java.nio.file.Path 6 | 7 | /** 8 | * FileDocumentNotFoundException is thrown when a [Document] could not be found within a [FileRepository]. 9 | * 10 | * @see Document 11 | * @see DocumentNotFoundException 12 | * @see FileRepository 13 | */ 14 | class FileDocumentNotFoundException(id: String, path: Path) : 15 | DocumentNotFoundException("Could not find document with ID [$id] in path [$path]!") 16 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/file/FileRepository.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.file 2 | 3 | import org.livingdoc.repositories.Document 4 | import org.livingdoc.repositories.DocumentRepository 5 | import org.livingdoc.repositories.format.DocumentFormatManager 6 | 7 | /** 8 | * A FileRepository is a [DocumentRepository] in a locally mounted filesystem. 9 | * 10 | * @see DocumentRepository 11 | */ 12 | class FileRepository( 13 | private val name: String, 14 | private val config: FileRepositoryConfig, 15 | private val fileResolver: FileResolver = FileResolver() 16 | ) : DocumentRepository { 17 | 18 | override fun getDocument(documentIdentifier: String): Document { 19 | val file = fileResolver.resolveFile(config.documentRoot, documentIdentifier) 20 | return DocumentFormatManager.getFormat(file.extension()).parse(file.stream()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/file/FileRepositoryConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.file 2 | 3 | /** 4 | * FileRepositoryConfig contains the configuration for a [FileRepository]. 5 | * 6 | * @see FileRepository 7 | */ 8 | data class FileRepositoryConfig( 9 | /** 10 | * DocumentRoot is the directory path in which executable documents are located. 11 | */ 12 | var documentRoot: String = "documentation" 13 | ) 14 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/file/FileRepositoryFactory.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.file 2 | 3 | import org.livingdoc.config.YamlUtils 4 | import org.livingdoc.repositories.DocumentRepositoryFactory 5 | 6 | /** 7 | * A FileRepositoryFactory is a [DocumentRepositoryFactory] used to create [FileRepository] instances with a specific 8 | * [FileRepositoryConfig]. 9 | * 10 | * @see DocumentRepositoryFactory 11 | * @see FileRepository 12 | * @see FileRepositoryConfig 13 | */ 14 | class FileRepositoryFactory : DocumentRepositoryFactory { 15 | 16 | override fun createRepository(name: String, configData: Map): FileRepository { 17 | val config = YamlUtils.toObject(configData, FileRepositoryConfig::class) 18 | return FileRepository(name, config) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/main/kotlin/org/livingdoc/repositories/file/FileResolver.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.file 2 | 3 | import java.io.File 4 | import java.nio.file.Paths 5 | 6 | /** 7 | * Class to handle resolving file for the [FileRepository]. 8 | */ 9 | open class FileResolver { 10 | 11 | /** 12 | * Returns an [File] for the given document root and identifier. 13 | */ 14 | open fun resolveFile(documentRoot: String, documentIdentifier: String): DocumentFile { 15 | val file = Paths.get(documentRoot, documentIdentifier).toFile() 16 | when (file.exists()) { 17 | true -> return DocumentFile(file) 18 | false -> throw FileDocumentNotFoundException(documentIdentifier, file.toPath()) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/test/docs/FileRepositoryIntegrationTest.md: -------------------------------------------------------------------------------- 1 | # LivingDoc 2 | 3 | Examples 4 | 5 | | File-Name | Throws FileNotFoundException | 6 | |-----------|-----------------| 7 | | FileRepositoryIntegrationTestMarkdown.md | False | 8 | | ThisFileDoesNotExist.md | True | 9 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/test/kotlin/org/livingdoc/repositories/file/FileRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.file 2 | 3 | import org.junit.jupiter.api.Test 4 | import org.junit.jupiter.api.assertThrows 5 | 6 | internal class FileRepositoryTest { 7 | 8 | val cut = FileRepository("", FileRepositoryConfig()) 9 | 10 | @Test fun `exception is thrown if document could not be found`() { 11 | assertThrows { 12 | cut.getDocument("foo-bar.md") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/test/resources/FileRepositoryIntegrationTestMarkdown.md: -------------------------------------------------------------------------------- 1 | # Valid Test 2 | 3 | | Input | Expected Output | 4 | |-------|-----------------| 5 | | Input A | Ok | 6 | -------------------------------------------------------------------------------- /livingdoc-repository-file/src/test/resources/livingdoc.yml: -------------------------------------------------------------------------------- 1 | repositories: 2 | - name: "local" 3 | factory: "org.livingdoc.repositories.file.FileRepositoryFactory" 4 | config: 5 | documentRoot: "src/test/docs" -------------------------------------------------------------------------------- /livingdoc-repository-git/livingdoc-repository-git.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | implementation(project(":livingdoc-config")) 11 | implementation(project(":livingdoc-extensions-api")) 12 | implementation(project(":livingdoc-repositories")) 13 | 14 | implementation(group = "org.eclipse.jgit", name = "org.eclipse.jgit", version = Versions.jgit) 15 | 16 | testImplementation("ch.qos.logback:logback-classic:${Versions.logback}") 17 | testImplementation("org.assertj:assertj-core:${Versions.assertJ}") 18 | } 19 | -------------------------------------------------------------------------------- /livingdoc-repository-git/src/main/kotlin/org/livingdoc/repositories/git/GitDocumentIdentifier.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.git 2 | 3 | import org.eclipse.jgit.lib.Constants 4 | import java.io.File 5 | 6 | /** 7 | * A GitDocumentIdentifier identifies a document stored in a git repository by path and version 8 | */ 9 | internal class GitDocumentIdentifier(val path: String, private val version: String? = null) { 10 | val format: String 11 | get() = File(path).extension 12 | 13 | val revision 14 | get() = version ?: Constants.HEAD 15 | 16 | companion object { 17 | /** 18 | * parse extracts a document path and optional version from a documentIdentifier 19 | */ 20 | fun parse(documentIdentifier: String): GitDocumentIdentifier { 21 | val parts = documentIdentifier.split('@') 22 | 23 | if (2 < parts.size) { 24 | throw InvalidDocumentIdentifierException(documentIdentifier) 25 | } 26 | 27 | val path = parts[0] 28 | val version = parts.getOrNull(1).takeUnless { it.isNullOrBlank() } 29 | 30 | return GitDocumentIdentifier(path, version) 31 | } 32 | } 33 | } 34 | 35 | class InvalidDocumentIdentifierException(documentIdentifier: String) : 36 | Exception("Invalid document identifier: $documentIdentifier") 37 | -------------------------------------------------------------------------------- /livingdoc-repository-git/src/main/kotlin/org/livingdoc/repositories/git/GitRepository.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.git 2 | 3 | import org.livingdoc.repositories.Document 4 | import org.livingdoc.repositories.DocumentRepository 5 | import org.livingdoc.repositories.format.DocumentFormatManager 6 | 7 | /** 8 | * GitRepository can find [Documents][Document] in remote git repositories 9 | */ 10 | class GitRepository(val name: String, config: GitRepositoryConfig) : DocumentRepository { 11 | private val fileResolver = GitFileResolver(GitRepositoryResolver(config).resolve()) 12 | 13 | override fun getDocument(documentIdentifier: String): Document { 14 | val identifier = GitDocumentIdentifier.parse(documentIdentifier) 15 | 16 | val file = fileResolver.resolve(identifier) 17 | 18 | return DocumentFormatManager.getFormat(identifier.format).parse(file) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /livingdoc-repository-git/src/main/kotlin/org/livingdoc/repositories/git/GitRepositoryConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.git 2 | 3 | import org.livingdoc.repositories.cache.CacheConfiguration 4 | import org.livingdoc.repositories.cache.CacheHelper 5 | 6 | /** 7 | * This class contains the configuration for loading files from a remote git repository 8 | */ 9 | data class GitRepositoryConfig( 10 | var remoteUri: String = "", 11 | var username: String = "", 12 | var password: String = "", 13 | var cache: CacheConfiguration = CacheConfiguration( 14 | path = "livingdoc/build/git", 15 | cachePolicy = CacheHelper.CACHE_ALWAYS 16 | ) 17 | ) 18 | -------------------------------------------------------------------------------- /livingdoc-repository-git/src/main/kotlin/org/livingdoc/repositories/git/GitRepositoryFactory.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.git 2 | 3 | import org.livingdoc.config.YamlUtils 4 | import org.livingdoc.repositories.DocumentRepositoryFactory 5 | 6 | /** 7 | * GitRepositoryFactory creates [GitRepositories][GitRepository] to lookup documents in remote git repositories 8 | */ 9 | class GitRepositoryFactory : DocumentRepositoryFactory { 10 | override fun createRepository(name: String, configData: Map): GitRepository { 11 | val config = YamlUtils.toObject(configData, GitRepositoryConfig::class) 12 | 13 | return GitRepository(name, config) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /livingdoc-repository-git/src/test/kotlin/org/livingdoc/repositories/git/GitDocumentIdentifierTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.git 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Test 5 | import org.junit.jupiter.api.assertThrows 6 | 7 | internal class GitDocumentIdentifierTest { 8 | @Test 9 | fun `can parse simple path`() { 10 | val identifier = GitDocumentIdentifier.parse("Calculator.md") 11 | 12 | assertThat(identifier.path).isEqualTo("Calculator.md") 13 | assertThat(identifier.revision).isEqualTo("HEAD") 14 | } 15 | 16 | @Test 17 | fun `can parse path and version`() { 18 | val identifier = GitDocumentIdentifier.parse("Calculator.md@4f8fb05601e2bd84cf2fb05741ff5a868f285c6b") 19 | 20 | assertThat(identifier.path).isEqualTo("Calculator.md") 21 | assertThat(identifier.revision).isEqualTo("4f8fb05601e2bd84cf2fb05741ff5a868f285c6b") 22 | } 23 | 24 | @Test 25 | fun `throws if too many separators`() { 26 | assertThrows { 27 | GitDocumentIdentifier.parse("file@revision@revision") 28 | } 29 | } 30 | 31 | @Test 32 | fun `ignores empty revision`() { 33 | val identifier = GitDocumentIdentifier.parse("Calculator.md") 34 | 35 | assertThat(identifier.path).isEqualTo("Calculator.md") 36 | assertThat(identifier.revision).isEqualTo("HEAD") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /livingdoc-repository-git/src/test/kotlin/org/livingdoc/repositories/git/GitRepositoryFactoryTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.git 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Disabled 5 | import org.junit.jupiter.api.Test 6 | import org.livingdoc.repositories.DocumentRepositoryFactory 7 | 8 | @Disabled("This test requires configuration of a remote git repository") 9 | class GitRepositoryFactoryTest { 10 | private val cut: DocumentRepositoryFactory = GitRepositoryFactory() 11 | 12 | @Test 13 | fun `can create git repository`() { 14 | assertThat(cut.createRepository("git", emptyMap())).isInstanceOf(GitRepository::class.java) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-repository-git/src/test/kotlin/org/livingdoc/repositories/git/GitRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.git 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.junit.jupiter.api.Disabled 5 | import org.junit.jupiter.api.Test 6 | import org.junit.jupiter.api.assertThrows 7 | import org.livingdoc.repositories.DocumentNotFoundException 8 | import org.livingdoc.repositories.DocumentRepository 9 | import org.livingdoc.repositories.cache.CacheConfiguration 10 | 11 | @Disabled("This test requires configuration of a remote git repository") 12 | internal class GitRepositoryTest { 13 | private val cut: DocumentRepository = GitRepository( 14 | "git", GitRepositoryConfig( 15 | cache = CacheConfiguration( 16 | path = createTempDir().absolutePath 17 | ) 18 | ) 19 | ) 20 | 21 | @Test 22 | fun `can load document file`() { 23 | assertThat(cut.getDocument("TestTexts.md")).satisfies { document -> 24 | assertThat(document.elements).hasSize(4) 25 | } 26 | } 27 | 28 | @Test 29 | fun `throws if document cannot be found`() { 30 | assertThrows { 31 | cut.getDocument("NonExistentDocument.md") 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /livingdoc-repository-rest/livingdoc-repository-rest.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | dependencies { 6 | implementation("org.slf4j:slf4j-api:${Versions.slf4j}") 7 | implementation("io.ktor:ktor-client-apache:1.2.5") 8 | implementation(project(":livingdoc-config")) 9 | implementation(project(":livingdoc-extensions-api")) 10 | implementation(project(":livingdoc-repositories")) 11 | 12 | testRuntimeOnly(project(":livingdoc-junit-engine")) 13 | 14 | testImplementation("ch.qos.logback:logback-classic:${Versions.logback}") 15 | testImplementation("com.github.tomakehurst:wiremock-jre8:${Versions.wiremock}") 16 | testImplementation("org.assertj:assertj-core:${Versions.assertJ}") 17 | testImplementation(project(":livingdoc-api")) 18 | testImplementation(project(":livingdoc-repository-file")) 19 | testImplementation(project(":livingdoc-testdata")) 20 | } 21 | -------------------------------------------------------------------------------- /livingdoc-repository-rest/src/main/kotlin/org/livingdoc/repositories/rest/RESTDocumentNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.rest 2 | 3 | import org.livingdoc.repositories.DocumentNotFoundException 4 | 5 | /** 6 | * This exception is thrown when a document could not be found on the REST server. 7 | * 8 | * @see DocumentNotFoundException 9 | */ 10 | class RESTDocumentNotFoundException : DocumentNotFoundException { 11 | constructor(id: String, url: String) : super("Could not find document with ID [$id] on server [$url]!") 12 | constructor( 13 | throwable: Throwable, 14 | id: String, 15 | url: String 16 | ) : super("Could not find document with ID [$id] on server [$url]!", throwable) 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-repository-rest/src/main/kotlin/org/livingdoc/repositories/rest/RESTRepositoryConfig.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.rest 2 | 3 | import org.livingdoc.repositories.cache.CacheConfiguration 4 | 5 | /** 6 | * This class implements the configuration for a REST repository 7 | */ 8 | class RESTRepositoryConfig( 9 | var baseURL: String = "http://localhost/", 10 | var cacheConfig: CacheConfiguration = CacheConfiguration() 11 | ) 12 | -------------------------------------------------------------------------------- /livingdoc-repository-rest/src/main/kotlin/org/livingdoc/repositories/rest/RESTRepositoryFactory.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.rest 2 | 3 | import org.livingdoc.config.YamlUtils 4 | import org.livingdoc.repositories.DocumentRepositoryFactory 5 | 6 | /** 7 | * This Factory is used to create a RESTRepository. This Factory must be specified in the livingdoc.yml to use the REST 8 | * Repository. For more details about the {@see RESTRepository} see it's documentation. 9 | */ 10 | class RESTRepositoryFactory : DocumentRepositoryFactory { 11 | 12 | override fun createRepository(name: String, configData: Map): RESTRepository { 13 | val config = YamlUtils.toObject(configData, RESTRepositoryConfig::class) 14 | return RESTRepository(name, config) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /livingdoc-repository-rest/src/test/resources/livingdoc.yml: -------------------------------------------------------------------------------- 1 | repositories: 2 | - name: "local" 3 | factory: "org.livingdoc.repositories.file.FileRepositoryFactory" 4 | config: 5 | documentRoot: "src/test/docs" -------------------------------------------------------------------------------- /livingdoc-results/livingdoc-results.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | dependencies { 6 | implementation(project(":livingdoc-extensions-api")) 7 | implementation(project(":livingdoc-testdata")) 8 | implementation("org.slf4j:slf4j-api:${Versions.slf4j}") 9 | 10 | testImplementation("ch.qos.logback:logback-classic:${Versions.logback}") 11 | testImplementation("org.assertj:assertj-core:${Versions.assertJ}") 12 | } 13 | -------------------------------------------------------------------------------- /livingdoc-testdata/livingdoc-testdata.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | dependencies { 6 | implementation(project(":livingdoc-extensions-api")) 7 | } 8 | -------------------------------------------------------------------------------- /livingdoc-testdata/src/main/kotlin/org/livingdoc/repositories/model/decisiontable/DecisionTable.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.model.decisiontable 2 | 3 | import org.livingdoc.repositories.Document 4 | import org.livingdoc.repositories.model.TestData 5 | import org.livingdoc.repositories.model.TestDataDescription 6 | 7 | /** 8 | * DecisionTable contains all information that pertains to a decision table contained in a [Document] 9 | */ 10 | data class DecisionTable( 11 | val headers: List

, 12 | val rows: List, 13 | override val description: TestDataDescription = TestDataDescription(null, false, "") 14 | ) : TestData 15 | -------------------------------------------------------------------------------- /livingdoc-testdata/src/main/kotlin/org/livingdoc/repositories/model/decisiontable/Field.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.model.decisiontable 2 | 3 | /** 4 | * A Field contains a single value 5 | */ 6 | data class Field(val value: String) 7 | -------------------------------------------------------------------------------- /livingdoc-testdata/src/main/kotlin/org/livingdoc/repositories/model/decisiontable/Header.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.model.decisiontable 2 | 3 | /** 4 | * This data class is a representation of a single header of a [DecisionTable] example. 5 | * It provides access to the [String] representation of the headers name. 6 | */ 7 | data class Header(val name: String) 8 | -------------------------------------------------------------------------------- /livingdoc-testdata/src/main/kotlin/org/livingdoc/repositories/model/decisiontable/Row.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.model.decisiontable 2 | 3 | /** 4 | * A Row consists of [Fields][Field] for all [Headers][Header] of a [DecisionTable] 5 | */ 6 | data class Row(val headerToField: Map) 7 | -------------------------------------------------------------------------------- /livingdoc-testdata/src/main/kotlin/org/livingdoc/repositories/model/scenario/Scenario.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.model.scenario 2 | 3 | import org.livingdoc.repositories.Document 4 | import org.livingdoc.repositories.model.TestData 5 | import org.livingdoc.repositories.model.TestDataDescription 6 | 7 | /** 8 | * A Scenario represents a scenario contained in a [Document]. 9 | * 10 | * It consists of a list of [Steps][Step] and has an optional description. 11 | * 12 | * @see Document 13 | */ 14 | data class Scenario( 15 | val steps: List, 16 | override val description: TestDataDescription = TestDataDescription(null, false, "") 17 | ) : TestData 18 | -------------------------------------------------------------------------------- /livingdoc-testdata/src/main/kotlin/org/livingdoc/repositories/model/scenario/Step.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.repositories.model.scenario 2 | 3 | /** 4 | * A single Step of a [Scenario]. 5 | * 6 | * @see Scenario 7 | */ 8 | data class Step( 9 | val value: String 10 | ) 11 | -------------------------------------------------------------------------------- /livingdoc-tests/livingdoc-tests.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-project-config` 3 | } 4 | 5 | dependencies { 6 | testRuntime(project(":livingdoc-junit-engine")) 7 | testRuntime(project(":livingdoc-repository-file")) 8 | testRuntime(project(":livingdoc-repository-rest")) 9 | 10 | 11 | testImplementation(project(":livingdoc-api")) 12 | testImplementation(project(":livingdoc-converters")) 13 | testImplementation(project(":livingdoc-format-gherkin")) 14 | testImplementation("ch.qos.logback:logback-classic:${Versions.logback}") 15 | testImplementation("com.github.tomakehurst:wiremock-jre8:${Versions.wiremock}") 16 | testImplementation("org.assertj:assertj-core:${Versions.assertJ}") 17 | } 18 | -------------------------------------------------------------------------------- /livingdoc-tests/src/main/kotlin/org/livingdoc/example/Calculator.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.example 2 | 3 | /** 4 | * A simple calculator implementation 5 | * 6 | * This will be our first system under test (SUT) in this project 7 | */ 8 | class Calculator { 9 | 10 | /** 11 | * @return the sum of two numbers 12 | */ 13 | fun sum(a: Float, b: Float): Float { 14 | return a + b 15 | } 16 | 17 | /** 18 | * @param a the minuend 19 | * @param b the subtrahend 20 | * @return the difference of two numbers 21 | */ 22 | fun diff(a: Float, b: Float): Float { 23 | return a - b 24 | } 25 | 26 | /** 27 | * @return the product of two numbers 28 | */ 29 | fun multiply(a: Float, b: Float): Float { 30 | return a * b 31 | } 32 | 33 | /** 34 | * @param a the dividend 35 | * @param b the divisor 36 | * @return the quotient of two numbers 37 | */ 38 | fun divide(a: Float, b: Float): Float { 39 | return a / b 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /livingdoc-tests/src/main/kotlin/org/livingdoc/example/CalculatorInt.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.example 2 | 3 | class CalculatorInt { 4 | 5 | fun sum(a: Int, b: Int): Int { 6 | return a + b 7 | } 8 | 9 | fun diff(a: Int, b: Int): Int { 10 | return a - b 11 | } 12 | 13 | fun multiply(a: Int, b: Int): Int { 14 | return a * b 15 | } 16 | 17 | fun divide(a: Int, b: Int): Int { 18 | return a / b 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /livingdoc-tests/src/main/kotlin/org/livingdoc/example/CoffeeMachine.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.example 2 | 3 | class CoffeeMachine { 4 | fun getCoffee(): Boolean { 5 | return (deposit > 0.0f && leftCoffees > 0) 6 | } 7 | 8 | fun depositMoney(amount: Float): Boolean { 9 | if (!triggered) { 10 | deposit += amount 11 | return true 12 | } else { 13 | return false 14 | } 15 | } 16 | 17 | fun setLeftCoffees(amount: Int) { 18 | leftCoffees = amount 19 | } 20 | 21 | var triggered = false 22 | private var deposit: Float = 0.0f 23 | private var leftCoffees: Int = 0 24 | } 25 | -------------------------------------------------------------------------------- /livingdoc-tests/src/main/kotlin/org/livingdoc/example/CustomType.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.example 2 | 3 | data class CustomType( 4 | val text: String, 5 | val number: Int 6 | ) 7 | -------------------------------------------------------------------------------- /livingdoc-tests/src/main/kotlin/org/livingdoc/example/Divider.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.example 2 | 3 | /** 4 | * A simple calculator implementation 5 | * 6 | * This will be our first system under test (SUT) in this project 7 | */ 8 | class Divider { 9 | 10 | /** 11 | * @param a the dividend 12 | * @param b the divisor 13 | * @return the quotient of two numbers 14 | */ 15 | @Throws(IllegalArgumentException::class) 16 | fun divide(a: Float, b: Float): Float { 17 | if (b == 0.0f) { 18 | throw IllegalArgumentException("Thrown by Divider") 19 | } 20 | return a / b 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /livingdoc-tests/src/main/kotlin/org/livingdoc/example/TextFunctions.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.example 2 | 3 | /** 4 | * Some string handling functions 5 | * 6 | * This will be our second system under test (SUT) in this project 7 | */ 8 | class TextFunctions { 9 | 10 | /** 11 | * @param a the first string 12 | * @param b the second string 13 | * @return the concatenation of both strings 14 | */ 15 | fun concStrings(a: String, b: String): String { 16 | return a + b 17 | } 18 | 19 | /** 20 | * @return returns a string representing floating-point zero 21 | */ 22 | fun nullifyString(): String { 23 | return "0.0F" 24 | } 25 | 26 | /** 27 | * @param a the first line 28 | * @param b the second line 29 | * @return a multiline representation of both strings 30 | */ 31 | fun multiline(a: String, b: String): String { 32 | return "line 1: " + a + ", line 2: " + b 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/Calculator.feature: -------------------------------------------------------------------------------- 1 | Feature: Calculator 2 | Scenario: The calculator can add 3 | Given a calculator 4 | When I add 2 and 3 5 | Then I get 5 6 | But result is less than 10 7 | And result is greater than 0 -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/CalculatorEmptyCell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

A big description text to explain the first test!

4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
aba + b = ?a - b = ?a * b = ?a / b = ?
11011
1010Infinity
32 | 33 |

A big description text to explain the second test!

34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
xya + b = ?a - b = ?a * b = ?a / b = ?
11211
1011Infinity
62 | 63 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/CalculatorEmptyCell.md: -------------------------------------------------------------------------------- 1 | # Calculator 2 | 3 | Examples 4 | 5 | | a | b | a + b = ? | a - b = ? | a * b = ? | a / b = ? | 6 | |----|----|-----------|-----------|-----------|-----------| 7 | | -1 | -1 | | 0 | 1 | 1 | 8 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/CalculatorGherkin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Gherkin Test

4 |
 5 |     
 6 | Feature: Calculator
 7 |   Scenario: The calculator can add
 8 |     Given a calculator
 9 |     When I add 2 and 3
10 |     Then I get 5
11 |     But result is less than 10
12 |     And result is greater than 0
13 |     
14 |   
15 |     Feature: Serve coffee
16 |       In order to earn money
17 |       Customers should be able to
18 |       buy coffee at all times
19 | 
20 |     Scenario: Buy last coffee
21 |       Given there are 1 coffees left in the machine
22 |       And I have deposited 1 dollar
23 |       When I press the coffee button
24 |       Then I should be served a coffee
25 |   
26 | 
27 | 28 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/CalculatorInt.md: -------------------------------------------------------------------------------- 1 | # Calculator 2 | 3 | The CalculatorInt is used to test Exceptions as expected output. 4 | 5 | Examples 6 | 7 | | a | b | a * b = ? | a / b = ? | 8 | |----|----|-----------|-----------| 9 | | -1 | -1 | 1 | 1 | 10 | | 1 | 0 | 0 | error | 11 | | -1 | 0 | -0 | error | 12 | 13 | # Scenario 14 | 15 | - adding 1 and 2 equals 3 16 | - dividing 1 by 0 equals error 17 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/CalculatorManual.md: -------------------------------------------------------------------------------- 1 | # Calculator 2 | 3 | ## This test is MANUAL 4 | 5 | | a | b | a + b = ? | 6 | |----|----|-----------| 7 | | 0 | 0 | 0 | 8 | 9 | # MANUAL Scenario 2 10 | 11 | - add 9 to itself and you get 18 12 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/ConfluenceCheckbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Test 1

4 |

This html file pretends to have a confluence checkbox in a td

5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 |
aba and b = ?
truetrue 16 |
    17 |
  • ok
  • 18 |
19 |
truefalsefalse
truefalse 30 |
    31 |
  • ok
  • 32 |
33 |
36 |

A description after the first test.

37 | 38 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/DividerFailFast.md: -------------------------------------------------------------------------------- 1 | 2 | # Scenario 1 3 | 4 | - divide 2 by 1 equals 2 5 | - divide 4 by 2 equals 2 6 | - divide 6 by 3 equals 2 7 | 8 | 9 | # Calculator 10 | 11 | Examples 12 | 13 | | a | b | a / b = ? | 14 | |----|----|-----------| 15 | | 2 | 1 | 2 | 16 | | 2 | 0 | 1 | 17 | | 4 | 2 | 2 | 18 | 19 | 20 | | a | b | a / b = ? | 21 | |----|----|-----------| 22 | | 5 | 2 | 2.5 | 23 | | 21 | 7 | 3 | 24 | |121 | 11 | 11 | 25 | 26 | # Scenario 2 27 | 28 | - divide 2 by 0 equals 1 29 | 30 | # Scenario 3 31 | 32 | - divide 4 by 2 equals 2 33 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/GermanCalculator.feature: -------------------------------------------------------------------------------- 1 | # language: de 2 | Funktionalität: Taschenrechner 3 | Szenario: Der Taschenrechner kann addieren 4 | Gegeben sei ein Taschenrechner 5 | Wenn ich 2 und 3 addiere 6 | Dann erhalte ich 5 7 | Und das Ergebnis ist größer als 0 8 | Aber das Ergebnis ist kleiner als 10 9 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/MultilineArgumentCalculator.feature: -------------------------------------------------------------------------------- 1 | Feature: Calculator 2 | Scenario: The calculator can process data tables 3 | Given a calculator 4 | When I perform: 5 | | 2 | 3 | 5 | 6 | | 1 | 1 | 2 | 7 | 8 | Scenario: The calculator can process doc strings 9 | Given a calculator 10 | When I read 11 | """ 12 | A long long text without any numbers 13 | """ 14 | Then I do nothing 15 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/OutlineCalculator.feature: -------------------------------------------------------------------------------- 1 | Feature: Calculator 2 | Scenario: The calculator can add 3 | Given a calculator 4 | When I add and 5 | Then I get 6 | But result is less than 7 | And result is greater than 8 | 9 | Examples: 10 | | summand1 | summand2 | sum | upperbound | lowerbound | 11 | | 1 | 2 | 3 | 4 | 0 | 12 | | 123 | 123 | 246 | 256 | 0 | 13 | | 0 | 0 | 0 | 1 | -1 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/TestTexts.md: -------------------------------------------------------------------------------- 1 | # Scenarios 2 | 3 | # Test A 4 | 5 | - concatenate {a} and {b} will result in {a}{b} 6 | - concatenate ddd and dd will result in ddddd 7 | 8 | # Test B 9 | 10 | - concatenate () and () will result in ()() 11 | 12 | # Test C 13 | 14 | this test will also run with strange characters 15 | 16 | - concatenate bla and 〈〉 will result in bla〈〉 17 | 18 | # Test D 19 | 20 | - nullifying cdf and rising will give us 0.0F as output 21 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/docs/TypeConverter.md: -------------------------------------------------------------------------------- 1 | # Scenarios 2 | 3 | # Test A 4 | 5 | - CustomType {"text":"bla","number":17} has property values bla and 17 6 | 7 | 8 | # Test B 9 | 10 | - The ColorConverter convert green to #008000 11 | - The ColorConverter convert #000 to #000000 12 | - The ColorConverter convert rgb(64,0,255) to #4000ff 13 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/kotlin/org/livingdoc/example/CalculatorDocumentMdManual.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.example 2 | 3 | import org.livingdoc.api.documents.ExecutableDocument 4 | import org.livingdoc.api.tagging.Tag 5 | 6 | /** 7 | * The manual test feature also works with Markdown documents 8 | */ 9 | @Tag("markdown") 10 | @ExecutableDocument("local://CalculatorManual.md") 11 | class CalculatorDocumentMdManual { 12 | // The test will fail since there are no matching fixtures, unless the MANUAL tests are correctly skipped. 13 | } 14 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/kotlin/org/livingdoc/example/TypeConverterMD.kt: -------------------------------------------------------------------------------- 1 | package org.livingdoc.example 2 | 3 | import org.assertj.core.api.Assertions.assertThat 4 | import org.livingdoc.api.conversion.Converter 5 | import org.livingdoc.api.documents.ExecutableDocument 6 | import org.livingdoc.api.fixtures.scenarios.Binding 7 | import org.livingdoc.api.fixtures.scenarios.ScenarioFixture 8 | import org.livingdoc.api.fixtures.scenarios.Step 9 | import org.livingdoc.converters.JSONConverter 10 | import org.livingdoc.converters.color.ColorConverter 11 | 12 | @ExecutableDocument("local://TypeConverter.md") 13 | class TypeConverterMD { 14 | 15 | @ScenarioFixture 16 | class ScenarioTests { 17 | 18 | @Step("CustomType {json} has property values {string} and {number}") 19 | fun testCustomTypeJson( 20 | @Binding("json") @Converter(JSONConverter::class) json: CustomType, 21 | @Binding("string") string: String, 22 | @Binding("number") number: Int 23 | ) { 24 | assertThat(json.text).isEqualTo(string) 25 | assertThat(json.number).isEqualTo(number) 26 | } 27 | 28 | @Step("The ColorConverter convert {color} to {hex}") 29 | fun testColorConverter( 30 | @Binding("color") @Converter(ColorConverter::class) color: String, 31 | @Binding("hex") hex: String 32 | ) { 33 | assertThat(color).isEqualTo(hex) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/resources/__files/TestingCache.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rest Repository Test file 6 | 7 | 8 | test 1 9 |

A big description text to explain the first test!

10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
aba + b = ?a - b = ?a * b = ?a / b = ?
112011
10110Infinity
37 | 38 | 39 | -------------------------------------------------------------------------------- /livingdoc-tests/src/test/resources/livingdoc.yml: -------------------------------------------------------------------------------- 1 | repositories: 2 | - name: "local" 3 | factory: "org.livingdoc.repositories.file.FileRepositoryFactory" 4 | config: 5 | documentRoot: "src/test/docs" 6 | - name: "rest" 7 | factory: "org.livingdoc.repositories.rest.RESTRepositoryFactory" 8 | config: 9 | baseURL: "http://localhost:8080/" 10 | cacheConfig: 11 | path: "build/livingdoc/cache/" 12 | cachePolicy: "cacheOnce" 13 | reports: 14 | - name: "default" 15 | format: "html" 16 | config: 17 | outputDir: "build/livingdoc/reports/html/" 18 | generateIndex: true 19 | - name: "json" 20 | format: "json" 21 | config: 22 | outputDir: "build/livingdoc/reports/json/" 23 | tags: 24 | exclude: 25 | - "slow" 26 | --------------------------------------------------------------------------------