From 919b271b5cbae234da6b5cfdea8dda0a7ee7fe3b Mon Sep 17 00:00:00 2001 From: Svante Schubert Date: Tue, 1 May 2018 11:19:36 +0200 Subject: [PATCH] Migration ZUGFeRD1->2 feature: Allowing transformation of complete input directory, with reference check for any ref file existent with line base comparison for better usability. Including several refactoring and tweaks. --- dependency-reduced-pom.xml | 8 +- .../ZUGFeRD/ZUGFeRDMigrator.java | 3 +- .../ZUGFeRD/ResourceUtilities.java | 196 +++++++++++++++++ .../ZUGFeRD/UpdateZUGFeRDTest.java | 88 -------- .../ZUGFeRD/VersionMigrationTest.java | 144 +++++++++++++ .../input/ZUGFeRD1-invoice-adjusted.xml | 197 ++++++++++++++++++ .../input/ZUGFeRD1-invoice.xml} | 0 .../reference/ZUGFeRD2-invoice.xml} | 0 8 files changed, 540 insertions(+), 96 deletions(-) create mode 100644 src/test/java/org/mustangproject/ZUGFeRD/ResourceUtilities.java delete mode 100644 src/test/java/org/mustangproject/ZUGFeRD/UpdateZUGFeRDTest.java create mode 100644 src/test/java/org/mustangproject/ZUGFeRD/VersionMigrationTest.java create mode 100644 src/test/resources/migration/input/ZUGFeRD1-invoice-adjusted.xml rename src/test/resources/{ZUGFeRD1-invoice_test-input.xml => migration/input/ZUGFeRD1-invoice.xml} (100%) rename src/test/resources/{ZUGFeRD2-invoice_output-reference.xml => migration/reference/ZUGFeRD2-invoice.xml} (100%) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index ee13d637..2faa6d2c 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -56,7 +56,7 @@ maven-compiler-plugin - 2.3.2 + 3.6.1 @@ -201,7 +201,7 @@ junit junit - 4.11 + 4.12 test @@ -228,10 +228,6 @@ ph-jaxb com.helger - - Saxon-HE - net.sf.saxon - diff --git a/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDMigrator.java b/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDMigrator.java index e6aeca5c..9f7cbe20 100644 --- a/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDMigrator.java +++ b/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRDMigrator.java @@ -6,8 +6,7 @@ * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also - * obtain a copy of the License at http://odftoolkit.org/docs/license.txt + * of the License at http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT diff --git a/src/test/java/org/mustangproject/ZUGFeRD/ResourceUtilities.java b/src/test/java/org/mustangproject/ZUGFeRD/ResourceUtilities.java new file mode 100644 index 00000000..dafbe570 --- /dev/null +++ b/src/test/java/org/mustangproject/ZUGFeRD/ResourceUtilities.java @@ -0,0 +1,196 @@ +/** ********************************************************************** + * + * Use is subject to license terms. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *********************************************************************** */ +package org.mustangproject.ZUGFeRD; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.logging.Level; +import java.util.logging.Logger; + + + +/** This test utility class is providing usability functions for test file resources */ +public class ResourceUtilities { + private static final Logger LOG = Logger.getLogger(ResourceUtilities.class.getName()); + + /** Loading a File into a String using a certain encoding and file path */ + public static String readFile(Charset encoding, String path) throws IOException { + byte[] content = Files.readAllBytes(Paths.get(path)); + return new String(content, encoding); + } + + /** Saving a String as a file to a certain file path */ + public static void saveFile(Charset encoding, String path, String content) throws FileNotFoundException { + + // resources will be released automatically + try (PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File(path)), encoding), true)) { + out.println(content); + } + } + + + /** The relative path of the test file will be resolved and the absolute will be returned + * @param relativeFilePath Path of the test resource relative to src/test/resource/. + * @return the absolute path of the test file + * @throws FileNotFoundException If the file could not be found + */ + public static String getAbsolutePath(String relativeFilePath) throws FileNotFoundException { + URI uri = null; + try { + uri = ResourceUtilities.class.getClassLoader().getResource(relativeFilePath).toURI(); + uri = new URI(toExternalForm(uri)); + } catch (URISyntaxException ex) { + LOG.log(Level.SEVERE, null, ex); + } + if (uri == null) { + throw new FileNotFoundException("Could not find the file '" + relativeFilePath + "'!"); + } + return uri.getPath(); + } + + /** The relative path of the test file will be resolved and the absolute will be returned + * @param relativeFilePath Path of the test resource relative to src/test/resource/. + * @return the URI created based on the relativeFilePath + * @throws URISyntaxException if no URI could be created from the given relative path + */ + public static URI getURI(String relativeFilePath) throws URISyntaxException { + String filePath = "file:" + ResourceUtilities.class.getClassLoader().getResource(relativeFilePath).getPath(); + filePath = toExternalForm(new URI(filePath)); + return new URI(filePath); + } + + /** The relative path of the test file will be used to determine an absolute + * path to a temporary directory in the output directory. + * @param relativeFilePath Path of the test resource relative to src/test/resource/. + * @return absolute path to a test output + * @throws IOException if no absolute Path could be created. + */ + public static String getTestOutput(String relativeFilePath) throws IOException { + return File.createTempFile(relativeFilePath, null).getAbsolutePath(); + } + + /** The Input of the test file will be resolved and the absolute will be returned + * @param relativeFilePath Path of the test resource relative to src/test/resource/. + * @return the absolute path of the test file + */ + public static InputStream getTestResourceAsStream(String relativeFilePath) { + return ResourceUtilities.class.getClassLoader().getResourceAsStream(relativeFilePath); + } + + /** Relative to the test output directory a test file will be returned dependent on the relativeFilePath provided. + * @param relativeFilePath Path of the test output resource relative to target/test-classes/. + * @return the empty File of the test output (to be filled) + */ + public static File newTestOutputFile(String relativeFilePath) { + String filepath = null; + try { + filepath = ResourceUtilities.class.getClassLoader().getResource("").toURI().getPath() + relativeFilePath; + } catch (URISyntaxException ex) { + LOG.log(Level.SEVERE, null, ex); + } + return new File(filepath); + } + + /** + * @return the absolute path of the test output folder, which is usually target/test-classes/. + */ + public static String getTestOutputFolder() { + String testFolder = null; + try { + testFolder = ResourceUtilities.class.getClassLoader().getResource("").toURI().getPath(); + } catch (URISyntaxException ex) { + LOG.log(Level.SEVERE, null, ex); + } + return testFolder; + } + + public static File getTempTestDirectory() { + File tempDir = new File(ResourceUtilities.getTestOutputFolder() + "temp"); + tempDir.mkdir(); //if it already exist no problem + return tempDir; + } + +/** + * To fix the 3 slashes bug for File URI: For example: + * file:/C:/work/test.txt -> file:///C:/work/test.txt + * + * @param u - the File URI + * @return the String of the URI + */ + public static String toExternalForm(URI u) { + StringBuilder sb = new StringBuilder(); + if (u.getScheme() != null) { + sb.append(u.getScheme()); + sb.append(':'); + } + if (u.isOpaque()) { + sb.append(u.getSchemeSpecificPart()); + } else { + if (u.getHost() != null) { + sb.append("//"); + if (u.getUserInfo() != null) { + sb.append(u.getUserInfo()); + sb.append('@'); + } + boolean needBrackets = ((u.getHost().indexOf(':') >= 0) && !u.getHost().startsWith("[") && !u.getHost().endsWith("]")); + if (needBrackets) + sb.append('['); + sb.append(u.getHost()); + if (needBrackets) + sb.append(']'); + if (u.getPort() != -1) { + sb.append(':'); + sb.append(u.getPort()); + } + } else if (u.getRawAuthority() != null) { + sb.append("//"); + sb.append(u.getRawAuthority()); + } else { + sb.append("//"); + } + if (u.getRawPath() != null) + sb.append(u.getRawPath()); + if (u.getRawQuery() != null) { + sb.append('?'); + sb.append(u.getRawQuery()); + } + } + if (u.getFragment() != null) { + sb.append('#'); + sb.append(u.getFragment()); + } + String ret = null; + try { + ret = new URI(sb.toString()).toASCIIString(); + } catch (URISyntaxException ex) { + LOG.log(Level.SEVERE, null, ex); + } + return ret; + } + private ResourceUtilities() { + } +} diff --git a/src/test/java/org/mustangproject/ZUGFeRD/UpdateZUGFeRDTest.java b/src/test/java/org/mustangproject/ZUGFeRD/UpdateZUGFeRDTest.java deleted file mode 100644 index a45abcb1..00000000 --- a/src/test/java/org/mustangproject/ZUGFeRD/UpdateZUGFeRDTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/** ********************************************************************** - * - * Copyright 2018 Jochen Staerk - * - * Use is subject to license terms. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also - * obtain a copy of the License at http://odftoolkit.org/docs/license.txt - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - * - *********************************************************************** */ -package org.mustangproject.ZUGFeRD; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.xml.transform.TransformerException; -import org.junit.Assert; -import org.junit.Test; - -public class UpdateZUGFeRDTest { - - private static final Logger LOG = Logger.getLogger(UpdateZUGFeRDTest.class.getName()); - - private static final String TEST_INPUT_NAME = "ZUGFeRD1-invoice_test-input.xml"; - private static final String TEST_OUTPUT_NAME = "ZUGFeRD2-invoice_test-output.xml"; - private static final String TEST_REF_NAME = "ZUGFeRD2-invoice_output-reference.xml"; - - private static final String TEST_INPUT_DIR = "src" + File.separator + "test" + File.separator + "resources" + File.separator; - private static final String TEST_OUTPUT_DIR = "target" + File.separator + "test-classes" + File.separator; - - private static String readFile(Charset encoding, String path) throws IOException { - byte[] encoded = Files.readAllBytes(Paths.get(path)); - return new String(encoded, encoding); - } - - private static void saveFile(String content, String path) throws FileNotFoundException { - try (PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File(path)), StandardCharsets.UTF_8), true)) { - out.println(content); - } - } - - @Test - public void testMigration() { - try { - String tmp = new ZUGFeRDMigrator().migrateFromV1ToV2(TEST_INPUT_DIR + TEST_INPUT_NAME); - saveFile(tmp, TEST_OUTPUT_DIR + TEST_OUTPUT_NAME); - LOG.log(Level.INFO, "***\nZUGFeRD 2.0:\n***\n{0}", tmp); - - // we need to save the string and reload it otherwise two bytes are missing (likely EOF related) - String refXML = readFile(StandardCharsets.UTF_8, TEST_INPUT_DIR + TEST_REF_NAME); - String outXML = readFile(StandardCharsets.UTF_8, TEST_OUTPUT_DIR + TEST_OUTPUT_NAME); - int t = outXML.length(); - int r = refXML.length(); - if (t != r || !(outXML.equals(refXML))) { - LOG.info("Please compare:" - + "\nZUGFeRD 2.0 Test Output: " + TEST_OUTPUT_DIR + TEST_OUTPUT_NAME - + "\nZUGFeRD 2.0 Test Reference: " + TEST_INPUT_DIR + TEST_REF_NAME); - LOG.info("\n\nFile sizes:\n " - + "\nZUGFeRD 2.0 Test Output: " + t - + "\nZUGFeRD 2.0 Test Reference: " + r); - Assert.fail("Version update failed, as test result and reference are different!"); - }else{ - LOG.log(Level.INFO, "***\nZUGFeRD 2.0 invoice:\n***\n{0}", outXML); - } - } catch (IOException | TransformerException t) { - LOG.log(Level.SEVERE, t.getMessage(), t); - Assert.fail("Failed with " + t.getClass().getName() + ": '" + t.getMessage() + "'"); - } - } -} diff --git a/src/test/java/org/mustangproject/ZUGFeRD/VersionMigrationTest.java b/src/test/java/org/mustangproject/ZUGFeRD/VersionMigrationTest.java new file mode 100644 index 00000000..081f0ccc --- /dev/null +++ b/src/test/java/org/mustangproject/ZUGFeRD/VersionMigrationTest.java @@ -0,0 +1,144 @@ +/** ********************************************************************** + * + * Use is subject to license terms. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + *********************************************************************** */ +package org.mustangproject.ZUGFeRD; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.transform.TransformerException; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + + +/** + * Every ZUGFeRD 1.0 XML file from src/test/resources/migration/input + * will be migrated to src/test/resources/migration/output. + * If there is a similar named file as test reference src/test/resources/migration/reference a test + * will be triggered for every single file. + * + * Note: Any 'ZUGFeRD1' will be exchanged to 'ZUGFeRD2' during migration and adequate reference will be searched for. + */ +@RunWith(Parameterized.class) +public class VersionMigrationTest { + + private static final Logger LOG = Logger.getLogger(VersionMigrationTest.class.getName()); + + private static final String INPUT_DIR = "src" + File.separator + "test" + File.separator + "resources" + File.separator + "migration" + File.separator + "input" + File.separator; + private static final String REFERENCE_DIR = "src" + File.separator + "test" + File.separator + "resources" + File.separator + "migration" + File.separator + "reference" + File.separator; + private static final String OUTPUT_DIR = "target" + File.separator + "test-classes" + File.separator + "migration" + File.separator + "output" + File.separator; + private File mTestFile = null; + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + // Creating the output directory for the tests + new File(OUTPUT_DIR).mkdirs(); + } + + public VersionMigrationTest(File testFile) { + mTestFile = testFile; + } + + @Parameterized.Parameters(name = "Test# {index}: {0}") + public static Collection data() { + Collection testSuiteData = new ArrayList(); + addFilesFromFolder(new File(INPUT_DIR), testSuiteData); + return testSuiteData; + } + + private static void addFilesFromFolder(final File folder, Collection testSuiteData) { + String filePath = null; + for (final File fileEntry : folder.listFiles()) { + filePath = fileEntry.getAbsolutePath(); + if (fileEntry.isDirectory()) { + LOG.log(Level.INFO, "*** testDirectory:{0}", filePath); + addFilesFromFolder(fileEntry, testSuiteData); + } else { + LOG.log(Level.INFO, "*** testFile: {0}", filePath); + Object[] testData = new Object[]{fileEntry}; + testSuiteData.add(testData); + } + } + } + + @Test + /** + * ZUGFeRD 1.0 to 2.0 migration test. + * For more information see class description. */ + public void testFile() { + testMigration(mTestFile.getName()); + } + + private void testMigration(String fileName) { + try { + String tmp = new ZUGFeRDMigrator().migrateFromV1ToV2(INPUT_DIR + fileName); + String newName = fileName.replace("ZUGFeRD1", "ZUGFeRD2"); + ResourceUtilities.saveFile(StandardCharsets.UTF_8, OUTPUT_DIR + newName, tmp); + if(new File(REFERENCE_DIR + newName).exists()){ + // we need to save the string and reload it otherwise two bytes are missing (likely EOF related) + String outXML = ResourceUtilities.readFile(StandardCharsets.UTF_8, OUTPUT_DIR + newName); + String refXML = ResourceUtilities.readFile(StandardCharsets.UTF_8, REFERENCE_DIR + newName); + int t = outXML.length(); + int r = refXML.length(); + if (t != r || !(outXML.equals(refXML))) { + LOG.info("There are differences between:" + + "\nZUGFeRD 2.0 Test Output @ " + OUTPUT_DIR + newName + + "\nZUGFeRD 2.0 Test Reference @ " + REFERENCE_DIR + newName); + LOG.info("\n\nFile sizes:\n " + + "\nZUGFeRD 2.0 Test Output character count: " + t + + "\nZUGFeRD 2.0 Test Refer. character count: " + r); + findLineDiffs(outXML, refXML); + Assert.fail("Version update failed, as test result and reference are different!"); + }else{ + LOG.log(Level.INFO,"\n*** Tested successfull migration from ZUGFeRD 1.0 to 2.0: '" + newName + "'' ***\n", OUTPUT_DIR); + } + }else{ + LOG.log(Level.INFO,"\n*** Migrated from ZUGFeRD 1.0 to 2.0 invoice: '" + newName + "'' ***\n", OUTPUT_DIR); + } + } catch (IOException | TransformerException t) { + LOG.log(Level.SEVERE, t.getMessage(), t); + Assert.fail("Failed with " + t.getClass().getName() + ": '" + t.getMessage() + "'"); + } + } + + + + private static void findLineDiffs(String outXML, String refXML){ + Scanner outScanner = new Scanner(outXML); + Scanner refScanner = new Scanner(refXML); + String outLine = null; + String refLine = null; + while (outScanner.hasNextLine() && refScanner.hasNextLine()) { + outLine = outScanner.nextLine(); + refLine = refScanner.nextLine(); + if(!outLine.equals(refLine)){ + LOG.info("First line difference between reference and output file:" + + "\nRefLine: " + refLine + + "\nOutLine: " + outLine + "\n"); + } + } + refScanner.close(); + outScanner.close(); + } +} diff --git a/src/test/resources/migration/input/ZUGFeRD1-invoice-adjusted.xml b/src/test/resources/migration/input/ZUGFeRD1-invoice-adjusted.xml new file mode 100644 index 00000000..74bf378e --- /dev/null +++ b/src/test/resources/migration/input/ZUGFeRD1-invoice-adjusted.xml @@ -0,0 +1,197 @@ + + + + + false + + + urn:ferd:CrossIndustryDocument:invoice:1p0:extended + + + + RE-20170509/506 + RECHNUNG2 + 380 + + 20170510 + + + + + + Bei Spiel GmbH + + 12345 + Ecke 12 + Stadthausen + DE + + + 22/815/0815/4 + + + DE136695976 + + + + Theodor Est + + 88802 + Bahnstr. 42 + Spielkreis + DE + + + DE999999999 + + + + + + + 20170507 + + + + + RE-20170509/505 + EUR + + 42 + Überweisung + + DE88 2008 0000 0970 3757 00 + + + COBADEFFXXX + Commerzbank + + + + 11.20 + VAT + 160.00 + S + 7.00 + + + 63.84 + VAT + 336.00 + S + 19.00 + + + Zahlbar ohne Abzug bis zum 30.05.2017 + + 20170530 + + + + 496.00 + 0.00 + 0.00 + 496.00 + 75.04 + 571.04 + 571.04 + + + + + 1 + + + + 160.0000 + 1.0000 + + + 160.0000 + 1.0000 + + + + 1.0000 + + + + VAT + S + 7.00 + + + 160.00 + + + + Künstlerische Gestaltung (Stunde): Einer weiteren Beispielrechnung + + + + + + 2 + + + + 0.7900 + 1.0000 + + + 0.7900 + 1.0000 + + + + 400.0000 + + + + VAT + S + 19.00 + + + 316.00 + + + + Luftballon: Bunt, ca. 500ml + + + + + + 3 + + + + 0.1000 + 1.0000 + + + 0.1000 + 1.0000 + + + + 200.0000 + + + + VAT + S + 19.00 + + + 20.00 + + + + Heiße Luft pro Liter + + + + + diff --git a/src/test/resources/ZUGFeRD1-invoice_test-input.xml b/src/test/resources/migration/input/ZUGFeRD1-invoice.xml similarity index 100% rename from src/test/resources/ZUGFeRD1-invoice_test-input.xml rename to src/test/resources/migration/input/ZUGFeRD1-invoice.xml diff --git a/src/test/resources/ZUGFeRD2-invoice_output-reference.xml b/src/test/resources/migration/reference/ZUGFeRD2-invoice.xml similarity index 100% rename from src/test/resources/ZUGFeRD2-invoice_output-reference.xml rename to src/test/resources/migration/reference/ZUGFeRD2-invoice.xml