From 18741c648b88ba60c416a516b53db067fb23b9e5 Mon Sep 17 00:00:00 2001 From: homebeaver Date: Wed, 23 Oct 2024 00:28:50 +0200 Subject: [PATCH] Method AbstractCheckDigitTest.createInvalidCodes should return empty array if checkDigitLth is 0. This applays to VATIN in DK and SK. --- .../checkdigit/AbstractCheckDigitTest2.java | 345 ++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 src/test/java/org/apache/commons/validator/routines/checkdigit/AbstractCheckDigitTest2.java diff --git a/src/test/java/org/apache/commons/validator/routines/checkdigit/AbstractCheckDigitTest2.java b/src/test/java/org/apache/commons/validator/routines/checkdigit/AbstractCheckDigitTest2.java new file mode 100644 index 000000000..15311c442 --- /dev/null +++ b/src/test/java/org/apache/commons/validator/routines/checkdigit/AbstractCheckDigitTest2.java @@ -0,0 +1,345 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.commons.validator.routines.checkdigit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +/** + * Check Digit Test. + */ +public abstract class AbstractCheckDigitTest2 { + + private static final String POSSIBLE_CHECK_DIGITS = "0123456789 ABCDEFHIJKLMNOPQRSTUVWXYZ\tabcdefghijklmnopqrstuvwxyz!@£$%^&*()_+"; + + /** Logging instance */ + protected Log log = LogFactory.getLog(getClass()); + + /** Check digit routine being tested */ + protected int checkDigitLth = 1; + + /** Check digit routine being tested */ + protected CheckDigit routine; + + /** + * Array of valid code values These must contain valid strings *including* the check digit. + * + * They are passed to: CheckDigit.isValid(expects string including checkdigit) which is expected to return true and + * AbstractCheckDigitTest.createInvalidCodes() which mangles the last character to check that the result is now invalid. and the truncated string is passed + * to CheckDigit.calculate(expects string without checkdigit) the result is compared with the last character + */ + protected String[] valid; + + /** + * Array of invalid code values + * + * These are currently passed to both CheckDigit.calculate(expects a string without checkdigit) which is expected to throw an exception However that only + * applies if the string is syntactically incorrect; and CheckDigit.isValid(expects a string including checkdigit) which is expected to return false + * + * See https://issues.apache.org/jira/browse/VALIDATOR-344 for some dicussion on this + */ + protected String[] invalid = { "12345678A" }; + + /** Code value which sums to zero */ + protected String zeroSum = "0000000000"; + + /** Prefix for error messages */ + protected String missingMessage = "Code is missing"; + + /** + * Returns the check digit (i.e. last character) for a code. + * + * @param code The code + * @return The check digit + */ + protected String checkDigit(final String code) { + if (code == null || code.length() <= checkDigitLth) { + return ""; + } + final int start = code.length() - checkDigitLth; + return code.substring(start); + } + + // private static final String POSSIBLE_CHECK_DIGITS = "0123456789"; + /** + * Returns an array of codes with invalid check digits. + * + * @param codes Codes with valid check digits + * @return Codes with invalid check digits + */ + protected String[] createInvalidCodes(final String[] codes) { + final List list = new ArrayList<>(); + if (checkDigitLth == 0) { + return list.toArray(new String[0]); + } + + // create invalid check digit values + for (final String fullCode : codes) { + final String code = removeCheckDigit(fullCode); + final String check = checkDigit(fullCode); + for (int i = 0; i < POSSIBLE_CHECK_DIGITS.length(); i++) { + String c = checkDigitLth == 1 ? "" : POSSIBLE_CHECK_DIGITS.substring(i, i + 1); + for (int j = 0; j < POSSIBLE_CHECK_DIGITS.length(); j++) { + final String curr = POSSIBLE_CHECK_DIGITS.substring(j, j + 1) + c; + if (!curr.equals(check)) { + list.add(createCode(code, curr)); + } + } + } + } + + System.out.println("EUG " + list.size() + " createInvalidCodes created."); + return list.toArray(new String[0]); + } + + protected String createCode(final String code, final String cd) { + return code + cd; + } + + /** + * Returns a code with the Check Digit (i.e. last character) removed. + * + * @param code The code + * @return The code without the check digit + */ + protected String removeCheckDigit(final String code) { + if (code == null || code.length() <= checkDigitLth) { + return null; + } + return code.substring(0, code.length() - checkDigitLth); + } + + /** + * Tear Down - clears routine and valid codes. + */ + @AfterEach + protected void tearDown() { + valid = null; + routine = null; + } + + /** + * Test calculate() for invalid values. + */ + @Test + public void testCalculateInvalid() { + + if (log.isDebugEnabled()) { + log.debug("testCalculateInvalid() for " + routine.getClass().getName()); + } + + // test invalid code values + for (int i = 0; i < invalid.length; i++) { + try { + final String code = invalid[i]; + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Invalid Check Digit, Code=[" + code + "]"); + } + System.out.println("EUG" + i + " Testing Invalid Check Digit, Code=[" + code + "]"); + final String expected = checkDigit(code); + final String codeWithNoCheckDigit = removeCheckDigit(code); + if (codeWithNoCheckDigit == null) { + throw new CheckDigitException("Invalid Code=[" + code + "]"); + } + final String actual = routine.calculate(codeWithNoCheckDigit); + // If exception not thrown, check that the digit is incorrect instead + if (expected.equals(actual)) { + fail("Expected mismatch for " + code + " expected " + expected + " actual " + actual); + } + } catch (final CheckDigitException e) { + System.out.println("EUG" + e.getMessage() ); + // possible failure messages: + // Invalid ISBN Length ... + // Invalid Character[ ... + // Are there any others? + assertTrue(e.getMessage().startsWith("Invalid "), "Invalid Character[" + i + "]=" + e.getMessage()); +// WAS assertTrue("Invalid Character[" +i +"]=" + e.getMessage(), e.getMessage().startsWith("Invalid Character[")); + } + } + } + + /** + * Test calculate() for valid values. + */ + @Test + public void testCalculateValid() { + if (log.isDebugEnabled()) { + log.debug("testCalculateValid() for " + routine.getClass().getName()); + } + + // test valid values + for (int i = 0; i < valid.length; i++) { + final String code = removeCheckDigit(valid[i]); + final String expected = checkDigit(valid[i]); + try { + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Valid Check Digit, Code=[" + code + "] expected=[" + expected + "]"); + } + System.out.println(" " + i + " Testing calculate Valid Check Digit, Code=[" + code + "] expected=[" + expected + "]"); + assertEquals(expected, routine.calculate(code), "valid[" + i + "]: " + valid[i]); + } catch (final Exception e) { + fail("valid[" + i + "]=" + valid[i] + " threw " + e); + } + } + + } + + /** + * Test isValid() for invalid values. + */ + @Test + public void testIsValidFalse() { + if (log.isDebugEnabled()) { + log.debug("testIsValidFalse() for " + routine.getClass().getName()); + } + + // test invalid code values + for (int i = 0; i < invalid.length; i++) { + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Invalid Code=[" + invalid[i] + "]"); + } + String invalidCode = invalid[i]; + System.out.println(" " + i + " Testing Invalid Code=[" + invalidCode + "]"); + assertFalse(routine.isValid(invalidCode), "invalid[" + i + "]: " + invalidCode); + } + + // test invalid check digit values + final String[] invalidCheckDigits = createInvalidCodes(valid); + for (int i = 0; i < invalidCheckDigits.length; i++) { + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Invalid Check Digit, Code=[" + invalidCheckDigits[i] + "]"); + } + boolean res = routine.isValid(invalidCheckDigits[i]); + if (res) { + log.warn(" " + i + " Testing Invalid Check Digit, Code=[" + invalidCheckDigits[i] + "]"); + } else { + //System.out.println(" " + i + " Testing Invalid Check Digit, Code=[" + invalidCheckDigits[i] + "]"); + } + assertFalse(res, "invalid check digit[" + i + "]: " + invalidCheckDigits[i]); + } + } + + /** + * Test isValid() for valid values. + */ + @Test + public void testIsValidTrue() { + if (log.isDebugEnabled()) { + log.debug("testIsValidTrue() for " + routine.getClass().getName()); + } + + // test valid values + for (int i = 0; i < valid.length; i++) { + if (log.isDebugEnabled()) { + log.debug(" " + i + " Testing Valid Code=[" + valid[i] + "]"); + } + System.out.println(" " + i + " Testing Valid Code=[" + valid[i] + "]"); + assertTrue(routine.isValid(valid[i]), "valid[" + i + "]: " + valid[i]); + } + } + + /** + * Test missing code + */ + @Test + public void testMissingCode() { + + // isValid() null + assertFalse(routine.isValid(null), "isValid() Null"); + + // isValid() zero length + assertFalse(routine.isValid(""), "isValid() Zero Length"); + + // isValid() length 1 + // Don't use 0, because that passes for Verhoef (not sure why yet) + assertFalse(routine.isValid("9"), "isValid() Length 1"); + + // calculate() null + try { + routine.calculate(null); + fail("calculate() Null - expected exception"); + } catch (final Exception e) { + assertEquals(missingMessage, e.getMessage(), "calculate() Null"); + } + + // calculate() zero length + try { + routine.calculate(""); + fail("calculate() Zero Length - expected exception"); + } catch (final Exception e) { + assertEquals(missingMessage, e.getMessage(), "calculate() Zero Length"); + } + } + + /** + * Test check digit serialization. + */ + @Test + public void testSerialization() { + assumeTrue(routine instanceof Serializable); + // Serialize the check digit routine + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(routine); + oos.flush(); + } catch (final Exception e) { + fail(routine.getClass().getName() + " error during serialization: " + e); + } + + // Deserialize the test object + Object result = null; + try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray())) { + final ObjectInputStream ois = new ObjectInputStream(bais); + result = ois.readObject(); + } catch (final Exception e) { + fail(routine.getClass().getName() + " error during deserialization: " + e); + } + assertNotNull(result); + } + + /** + * Test zero sum + */ + @Test + public void testZeroSum() { + assertFalse(routine.isValid(zeroSum), "isValid() Zero Sum"); + try { + routine.calculate(zeroSum); + fail("Zero Sum - expected exception"); + } catch (final Exception e) { + assertEquals("Invalid code, sum is zero", e.getMessage(), "isValid() Zero Sum"); + } + } + +}