diff --git a/.github/workflows/gradle-build.yaml b/.github/workflows/gradle-build.yaml new file mode 100644 index 0000000..15aec2d --- /dev/null +++ b/.github/workflows/gradle-build.yaml @@ -0,0 +1,25 @@ +name: Build with Gradle + +on: [push, pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@ec92e829475ac0c2315ea8f9eced72db85bb337a # v3.0.0 + + - name: Build with Gradle Wrapper + run: ./gradlew build + diff --git a/.gitignore b/.gitignore index 77617a1..7b055ce 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,11 @@ gradle-app.setting # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # gradle/wrapper/gradle-wrapper.properties + +# Eclipse +.settings/ +.project +.classpath + +# macOS +.DS_Store \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 25da7c1..33a9766 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,9 @@ plugins { `maven-publish` `embedded-kotlin` `kotlin-dsl` + `groovy` + `jacoco` + `eclipse` } java { @@ -39,6 +42,14 @@ dependencies { implementation("org.apache.xmlgraphics:fop:2.6") implementation("net.sf.offo:fop-hyph:2.0") implementation("xalan:xalan:2.7.2") + + testImplementation(platform("org.spockframework:spock-bom:2.3-groovy-3.0")) + testImplementation("org.spockframework:spock-core") + testImplementation(gradleTestKit()) +} + +tasks.withType().configureEach { + useJUnitPlatform() } pluginBundle { diff --git a/src/test/groovy/com/github/ramonwirsch/fopRenderer/FopRendererPluginSpec.groovy b/src/test/groovy/com/github/ramonwirsch/fopRenderer/FopRendererPluginSpec.groovy new file mode 100644 index 0000000..a387c84 --- /dev/null +++ b/src/test/groovy/com/github/ramonwirsch/fopRenderer/FopRendererPluginSpec.groovy @@ -0,0 +1,146 @@ +package com.github.ramonwirsch.fopRenderer + +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.gradle.testkit.runner.GradleRunner + +import spock.lang.PendingFeature +import spock.lang.Specification +import spock.lang.TempDir +import spock.util.io.FileSystemFixture + +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +import java.nio.file.Files + +class FopRendererPluginSpec extends Specification { + + @TempDir + FileSystemFixture fsFixture + + def setup() { + fsFixture.create { + dir('xml') { + copyFromClasspath('/data/xml/not-well-formed.xml') + copyFromClasspath('/data/xml/well-formed-but-invalid.xml') + copyFromClasspath('/data/xml/HAM.xml') + } + dir('xsl') { + copyFromClasspath('/data/xsl/style1.xsl') + } + dir('xsd') { + copyFromClasspath('/data/xsd/airport.xsd') + } + fsFixture.file('build.gradle') << """ + plugins { + id 'com.github.ramonwirsch.FopRenderer' + } + """ + + } + + + } + + def "adds the fopRenderer extension to the project"() { + given: "a fresh project without the plugin applied" + def project = ProjectBuilder.builder().build() + assert project.extensions.findByName('fopRenderer') == null + + when: + project.pluginManager.apply("com.github.ramonwirsch.FopRenderer") + + then: + project.extensions.findByName('fopRenderer') != null + } + + def "adds the validate task to the project"() { + given: "a fresh project without the plugin applied" + def project = ProjectBuilder.builder().build() + assert project.tasks.findByName('validate') == null + + when: + project.pluginManager.apply("com.github.ramonwirsch.FopRenderer") + + then: + project.tasks.findByName('validate') != null + } + + def "successfully validates a valid file"() { + given: + fsFixture.file('build.gradle') << """ + fopRenderer { + schemas { + 'airports' { + files = file("xml/HAM.xml") + offlineSchema = file("xsd/airport.xsd") + } + } + } + """ + + when: + def result = GradleRunner.create() + .withProjectDir(fsFixture.getCurrentPath().toFile()) + .withArguments('validateAirports') + .withPluginClasspath() + .build() + + then: + result.task(":validateAirports").outcome == SUCCESS + } + + def "fails to validate an invalid file"() { + given: + fsFixture.file('build.gradle') << """ + fopRenderer { + schemas { + 'airports' { + files = file("xml/well-formed-but-invalid.xml") + offlineSchema = file("xsd/airport.xsd") + } + } + } + """ + + when: + def result = GradleRunner.create() + .withProjectDir(fsFixture.getCurrentPath().toFile()) + .withArguments('validateAirports') + .withPluginClasspath() + .build() + + then: + Exception e = thrown() + } + + def "successfully renders a valid file"() { + given: + fsFixture.file('build.gradle') << """ + fopRenderer { + render { + 'airports' { + stylesheet = file("xsl/style1.xsl") + rootSrc = file("xml/HAM.xml") + } + } + } + """ + and: "output file does not yet exists" + assert !Files.exists(fsFixture.resolve('build/doc/airports.pdf')) + + when: + def result = GradleRunner.create() + .withProjectDir(fsFixture.getCurrentPath().toFile()) + .withArguments('renderAirports') + .withPluginClasspath() + .build() + + then: + result.task(":renderAirports").outcome == SUCCESS + Files.exists(fsFixture.resolve('build/doc/airports.pdf')) + + } + +} diff --git a/src/test/groovy/com/github/ramonwirsch/fopRenderer/SchemaConfigExtensionSpec.groovy b/src/test/groovy/com/github/ramonwirsch/fopRenderer/SchemaConfigExtensionSpec.groovy new file mode 100644 index 0000000..969f63e --- /dev/null +++ b/src/test/groovy/com/github/ramonwirsch/fopRenderer/SchemaConfigExtensionSpec.groovy @@ -0,0 +1,68 @@ +package com.github.ramonwirsch.fopRenderer + +import org.gradle.api.Project +import org.gradle.api.ProjectConfigurationException +import org.gradle.testfixtures.ProjectBuilder + +import spock.lang.Specification + +class SchemaConfigExtensionSpec extends Specification { + + Project project + + SchemaConfigExtension ext + + def setup() { + project = ProjectBuilder.builder().build() + ext = new SchemaConfigExtension("foo", project) + } + + def "fails if no files are configured"() { + when: + ext.files + + then: + ProjectConfigurationException e = thrown() + } + + def "does not use inherent schemas per default"() { + expect: + !ext.useInherentSchemas + } + + def "has no default offline schema"() { + expect: + ext.offlineSchema == null + } + + def "uses the offline schema (if configured) as the schemaUri when no schemaUri is configured explicitly"() { + given: "an offline schema configured" + ext.offlineSchema = new File(project.buildDir, "offline.xsd") + + expect: + ext.schemaUri == new File(project.buildDir, "offline.xsd").toURI().toURL() + } + + def "uses the implicit as the schemaUri when neither a schemaUri nor an offline schema are configured explicitly"() { + given: "no offline schema configured" + assert ext.offlineSchema == null + + expect: + ext.schemaUri == new URL("http://auto-implicit-validation") + } + + def "uses the schemaUri when one is configured explicitly"() { + given: "a schemaUri configured" + ext.schemaUri = new URL("https://foo/bar.xsd") + + expect: "the schemaUri is the configured one" + ext.schemaUri == new URL("https://foo/bar.xsd") + + when: "an offline schema is configured" + ext.offlineSchema = new File("foo.xsd") + + then: "the schema is still the configured one" + ext.schemaUri == new URL("https://foo/bar.xsd") + } + +} diff --git a/src/test/groovy/com/github/ramonwirsch/fopRenderer/XSLTTransformationSpec.groovy b/src/test/groovy/com/github/ramonwirsch/fopRenderer/XSLTTransformationSpec.groovy new file mode 100644 index 0000000..eeafed4 --- /dev/null +++ b/src/test/groovy/com/github/ramonwirsch/fopRenderer/XSLTTransformationSpec.groovy @@ -0,0 +1,117 @@ +package com.github.ramonwirsch.fopRenderer + +import spock.lang.Issue +import spock.lang.PendingFeature +import spock.lang.Specification +import spock.lang.TempDir +import spock.util.io.FileSystemFixture + +class XSLTTransformationSpec extends Specification { + + @TempDir + FileSystemFixture fsFixture + + File transformed + + def setup() { + fsFixture.create { + dir('xml') { + copyFromClasspath('/data/xml/not-well-formed.xml') + copyFromClasspath('/data/xml/well-formed-but-invalid.xml') + copyFromClasspath('/data/xml/HAM.xml') + } + dir('xsl') { + copyFromClasspath('/data/xsl/style1.xsl') + copyFromClasspath('/data/xsl/xslt-version-test.xsl') + } + dir('xsd') { + copyFromClasspath('/data/xsd/airport.xsd') + } + } + + transformed = fsFixture.file('out/transformed.xml').toFile() + assert !transformed.exists() + } + + def "successfully transforms a valid source" () { + given: "a valid source" + def src = fsFixture.resolve('xml/HAM.xml').toFile() + assert src.canRead() + + and: "a valid stylesheet" + def stylesheet = fsFixture.resolve('xsl/style1.xsl').toFile() + assert stylesheet.canRead() + + and: "a target file which does not (yet) exist" + assert !transformed.exists() + + and: "an instance without parameters" + def instance = new XSLTTransformation(stylesheet, [:]) + + when: "transformation is invoked" + instance.transform(src, transformed) + + then: "the result file exists" + transformed.exists() + } + + def "fails to transform a bad source"() { + given: "a bad source" + def src = fsFixture.resolve('xml/not-well-formed.xml').toFile() + assert src.canRead() + + and: "a valid stylesheet" + def stylesheet = fsFixture.resolve('xsl/style1.xsl').toFile() + + and: "an instance without parameters" + def instance = new XSLTTransformation(stylesheet, [:]) + + when: "transformation is invoked" + instance.transform(src, transformed) + + then: "the transformation fails" + Exception e = thrown() + !transformed.exists() + } + + def "fails to transform a valid source using a bad stylesheet"() { + given: "a valid source" + def src = fsFixture.resolve('xml/HAM.xml').toFile() + assert src.canRead() + + and: "a bad stylesheet" + def stylesheet = fsFixture.resolve('xml/not-well-formed.xml').toFile() + + and: "an instance without parameters" + def instance = new XSLTTransformation(stylesheet, [:]) + + when: "transformation is invoked" + instance.transform(src, transformed) + + then: "the transformation fails" + Exception e = thrown() + !transformed.exists() + } + + @PendingFeature + @Issue("https://github.com/ramonwirsch/fopRenderer/issues/14") + def "supports xslt 2 stylesheets"() { + given: "a well-formed source" + def src = fsFixture.resolve('xml/HAM.xml').toFile() + assert src.canRead() + + and: "the version-test stylesheet" + def stylesheet = fsFixture.resolve('xsl/xslt-version-test.xsl').toFile() + + and: "an instance without parameters" + def instance = new XSLTTransformation(stylesheet, [:]) + + when: "transformation is invoked" + instance.transform(src, transformed) + + then: "the transformation succeeds" + transformed.exists() + fsFixture.resolve('out/transformed.xml').text == "3.0" + } + +} diff --git a/src/test/resources/data/xml/FRA.xml b/src/test/resources/data/xml/FRA.xml new file mode 100644 index 0000000..d13736f --- /dev/null +++ b/src/test/resources/data/xml/FRA.xml @@ -0,0 +1,10 @@ + + + + EDDF + FRA + Frankfurt Airport + Frankfurt + DE + + \ No newline at end of file diff --git a/src/test/resources/data/xml/HAM.xml b/src/test/resources/data/xml/HAM.xml new file mode 100644 index 0000000..4b7e72c --- /dev/null +++ b/src/test/resources/data/xml/HAM.xml @@ -0,0 +1,10 @@ + + + + EDDH + HAM + Hamburg Airport Helmut Schmidt + Hamburg + DE + + \ No newline at end of file diff --git a/src/test/resources/data/xml/PHX.xml b/src/test/resources/data/xml/PHX.xml new file mode 100644 index 0000000..1e3ac19 --- /dev/null +++ b/src/test/resources/data/xml/PHX.xml @@ -0,0 +1,10 @@ + + + + KPHX + PHX + Phoenix Sky Harbor International Airport + Phoenix + US + + \ No newline at end of file diff --git a/src/test/resources/data/xml/not-well-formed.xml b/src/test/resources/data/xml/not-well-formed.xml new file mode 100644 index 0000000..38f6e87 --- /dev/null +++ b/src/test/resources/data/xml/not-well-formed.xml @@ -0,0 +1 @@ +This is so bad. \ No newline at end of file diff --git a/src/test/resources/data/xml/well-formed-but-invalid.xml b/src/test/resources/data/xml/well-formed-but-invalid.xml new file mode 100644 index 0000000..6d509ce --- /dev/null +++ b/src/test/resources/data/xml/well-formed-but-invalid.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/src/test/resources/data/xsd/airport.xsd b/src/test/resources/data/xsd/airport.xsd new file mode 100644 index 0000000..b24aa33 --- /dev/null +++ b/src/test/resources/data/xsd/airport.xsd @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + diff --git a/src/test/resources/data/xsl/style1.xsl b/src/test/resources/data/xsl/style1.xsl new file mode 100644 index 0000000..57405cb --- /dev/null +++ b/src/test/resources/data/xsl/style1.xsl @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/data/xsl/xslt-version-test.xsl b/src/test/resources/data/xsl/xslt-version-test.xsl new file mode 100644 index 0000000..14c4f1e --- /dev/null +++ b/src/test/resources/data/xsl/xslt-version-test.xsl @@ -0,0 +1,9 @@ + + + + + + + + +