/* * Copyright (C) 2013 University of Dundee & Open Microscopy Environment. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package ome.services.blitz.test.utests; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import ome.services.blitz.repo.path.FilePathRestrictionInstance; import ome.services.blitz.repo.path.FilePathRestrictions; import ome.services.blitz.util.CurrentPlatform; import nl.javadude.assumeng.Assumption; import nl.javadude.assumeng.AssumptionListener; import org.apache.commons.collections.CollectionUtils; import org.testng.Assert; import org.testng.annotations.Listeners; import org.testng.annotations.Test; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; /** * @author m.t.b.carroll@dundee.ac.uk * @since 5.0 */ @Test(groups = { "fs" }) @Listeners(AssumptionListener.class) public class FilePathRestrictionsTest { /** * Test that an empty rule set cannot be combined. */ @Test(expectedExceptions=IllegalArgumentException.class) public void testCombineNoRules() { FilePathRestrictions.combineFilePathRestrictions(); } /** * Test that one may create rule sets using mostly nulls. */ @Test public void testNullSafety() { FilePathRestrictions.combineFilePathRestrictions( new FilePathRestrictions(null, null, null, null, ImmutableSet.of('A'))); } /** * Test that rule sets may be combined if they have safe characters in common. */ @Test public void testCombineSafeCharacters() { FilePathRestrictions.combineFilePathRestrictions( new FilePathRestrictions(null, null, null, null, ImmutableSet.of('A', 'B')), new FilePathRestrictions(null, null, null, null, ImmutableSet.of('B', 'C'))); } /** * Test that rule sets may not be combined if they do not have safe characters in common. */ @Test(expectedExceptions=IllegalArgumentException.class) public void testCombineNoSafeCharacters() { FilePathRestrictions.combineFilePathRestrictions( new FilePathRestrictions(null, null, null, null, ImmutableSet.of('A', 'B')), new FilePathRestrictions(null, null, null, null, ImmutableSet.of('C', 'D'))); } /** * Test that rule sets may be combined if their mappings have safe characters in common. */ @Test public void testCombineTransformation() { final SetMultimap<Integer, Integer> transformationMatrixX = HashMultimap.create(); transformationMatrixX.put(0, 65); transformationMatrixX.put(0, 66); final SetMultimap<Integer, Integer> transformationMatrixY = HashMultimap.create(); transformationMatrixY.put(0, 66); transformationMatrixY.put(0, 67); FilePathRestrictions.combineFilePathRestrictions( new FilePathRestrictions(transformationMatrixX, null, null, null, ImmutableSet.of('A')), new FilePathRestrictions(transformationMatrixY, null, null, null, ImmutableSet.of('A'))); } /** * Test that rule sets may not be combined if their mappings have only unsafe characters in common. */ @Test(expectedExceptions=IllegalArgumentException.class) public void testCombineUnsafeTransformation() { final SetMultimap<Integer, Integer> transformationMatrixX = HashMultimap.create(); transformationMatrixX.put(0, 65); transformationMatrixX.put(0, 1); final SetMultimap<Integer, Integer> transformationMatrixY = HashMultimap.create(); transformationMatrixY.put(0, 1); transformationMatrixY.put(0, 66); FilePathRestrictions.combineFilePathRestrictions( new FilePathRestrictions(transformationMatrixX, null, null, null, ImmutableSet.of('A')), new FilePathRestrictions(transformationMatrixY, null, null, null, ImmutableSet.of('A'))); } /** * Test that rule sets may not be combined if their mappings have no safe characters in common. */ @Test(expectedExceptions=IllegalArgumentException.class) public void testCombineNoTransformation() { final SetMultimap<Integer, Integer> transformationMatrixX = HashMultimap.create(); transformationMatrixX.put(0, 65); transformationMatrixX.put(0, 66); final SetMultimap<Integer, Integer> transformationMatrixY = HashMultimap.create(); transformationMatrixY.put(0, 67); transformationMatrixY.put(0, 68); FilePathRestrictions.combineFilePathRestrictions( new FilePathRestrictions(transformationMatrixX, null, null, null, ImmutableSet.of('A')), new FilePathRestrictions(transformationMatrixY, null, null, null, ImmutableSet.of('A'))); } /** * Assert that an actual multimap is as expected regardless of order. * @param actual the actual value * @param expected the expected value */ private void assertEqualMultimaps(Multimap<Integer, Integer> actual, Multimap<Integer, Integer> expected) { Assert.assertTrue(CollectionUtils.isEqualCollection(actual.keySet(), expected.keySet())); for (final Integer key : expected.keySet()) { Assert.assertTrue(CollectionUtils.isEqualCollection(actual.get(key), expected.get(key))); } } /** * Test that two complex sets of rules combined as expected. * (On a rainy day this test could be broken up into several smaller tests.) */ @Test public void testCombineRules() { /* these variables define the X set of rules to combine */ final SetMultimap<Integer, Integer> transformationMatrixX = HashMultimap.create(); final Set<String> unsafePrefixesX = new HashSet<String>(); final Set<String> unsafeSuffixesX = new HashSet<String>(); final Set<String> unsafeNamesX = new HashSet<String>(); final Set<Character> safeCharactersX = new HashSet<Character>(); /* these variables define the Y set of rules to combine */ final SetMultimap<Integer, Integer> transformationMatrixY = HashMultimap.create(); final Set<String> unsafePrefixesY = new HashSet<String>(); final Set<String> unsafeSuffixesY = new HashSet<String>(); final Set<String> unsafeNamesY = new HashSet<String>(); final Set<Character> safeCharactersY = new HashSet<Character>(); /* these variables define the expected result of combining X and Y */ final SetMultimap<Integer, Integer> transformationMatrixXY = HashMultimap.create(); final Set<String> unsafePrefixesXY = new HashSet<String>(); final Set<String> unsafeSuffixesXY = new HashSet<String>(); final Set<String> unsafeNamesXY = new HashSet<String>(); final Set<Character> safeCharactersXY = new HashSet<Character>(); /* automatically map control characters to the safe characters; * we will remove and replace any that are to be tested specially */ for (int codePoint = 0; codePoint < 0x100; codePoint++) { if (Character.getType(codePoint) == Character.CONTROL) { transformationMatrixXY.put(codePoint, 65); } } /* choose four control characters and remove them from the transformation matrix */ final Iterator<Integer> controlCodePointIterator = transformationMatrixXY.keySet().iterator(); final int controlCharacterP = controlCodePointIterator.next(); final int controlCharacterQ = controlCodePointIterator.next(); final int controlCharacterR = controlCodePointIterator.next(); final int controlCharacterS = controlCodePointIterator.next(); transformationMatrixXY.removeAll(controlCharacterP); transformationMatrixXY.removeAll(controlCharacterQ); transformationMatrixXY.removeAll(controlCharacterR); transformationMatrixXY.removeAll(controlCharacterS); /* set up test case for combining control character mappings */ transformationMatrixX.put(controlCharacterP, 65); transformationMatrixX.put(controlCharacterP, 67); transformationMatrixX.put(controlCharacterQ, 65); transformationMatrixX.put(controlCharacterQ, 66); transformationMatrixX.put(controlCharacterR, 66); transformationMatrixY.put(controlCharacterQ, 66); transformationMatrixY.put(controlCharacterR, 66); transformationMatrixY.put(controlCharacterS, 68); transformationMatrixXY.put(controlCharacterP, 65); transformationMatrixXY.put(controlCharacterP, 67); transformationMatrixXY.put(controlCharacterQ, 66); transformationMatrixXY.put(controlCharacterR, 66); transformationMatrixXY.put(controlCharacterS, 68); /* choose four non-control characters and remove them from the transformation matrix */ int[] normalCodePoints = new int[4]; int index = 0; int codePoint = 0; while (index < normalCodePoints.length) { if (Character.getType(codePoint) != Character.CONTROL) { normalCodePoints[index++] = codePoint; transformationMatrixXY.removeAll(codePoint); } codePoint++; } int normalCharacterP = normalCodePoints[0]; int normalCharacterQ = normalCodePoints[1]; int normalCharacterR = normalCodePoints[2]; int normalCharacterS = normalCodePoints[3]; /* set up test case for combining non-control character mappings */ transformationMatrixX.put(normalCharacterP, 65); transformationMatrixX.put(normalCharacterP, 67); transformationMatrixX.put(normalCharacterQ, 65); transformationMatrixX.put(normalCharacterQ, 66); transformationMatrixX.put(normalCharacterR, 66); transformationMatrixY.put(normalCharacterQ, 66); transformationMatrixY.put(normalCharacterR, 66); transformationMatrixY.put(normalCharacterS, 68); transformationMatrixXY.put(normalCharacterP, 65); transformationMatrixXY.put(normalCharacterP, 67); transformationMatrixXY.put(normalCharacterQ, 66); transformationMatrixXY.put(normalCharacterR, 66); transformationMatrixXY.put(normalCharacterS, 68); /* set up test cases for combining proscribed strings */ unsafePrefixesX.add("XP"); unsafePrefixesX.add("YP"); unsafePrefixesY.add("YP"); unsafePrefixesY.add("ZP"); unsafePrefixesXY.add("XP"); unsafePrefixesXY.add("YP"); unsafePrefixesXY.add("ZP"); unsafeSuffixesX.add("XS"); unsafeSuffixesX.add("YS"); unsafeSuffixesY.add("YS"); unsafeSuffixesY.add("ZS"); unsafeSuffixesXY.add("XS"); unsafeSuffixesXY.add("YS"); unsafeSuffixesXY.add("ZS"); unsafeNamesX.add("XN"); unsafeNamesX.add("YN"); unsafeNamesY.add("YN"); unsafeNamesY.add("ZN"); unsafeNamesXY.add("XN"); unsafeNamesXY.add("YN"); unsafeNamesXY.add("ZN"); /* set up test case for combining safe characters */ safeCharactersX.add('A'); safeCharactersX.add('B'); safeCharactersY.add('A'); safeCharactersXY.add('A'); /* perform the combination */ final FilePathRestrictions rulesX = new FilePathRestrictions(transformationMatrixX, unsafePrefixesX, unsafeSuffixesX, unsafeNamesX, safeCharactersX); final FilePathRestrictions rulesY = new FilePathRestrictions(transformationMatrixY, unsafePrefixesY, unsafeSuffixesY, unsafeNamesY, safeCharactersY); final FilePathRestrictions rulesXY = FilePathRestrictions.combineFilePathRestrictions(rulesX, rulesY); /* test that the combination is as expected in all respects */ Assert.assertTrue(CollectionUtils.isEqualCollection(rulesXY.safeCharacters, safeCharactersXY)); Assert.assertTrue(CollectionUtils.isEqualCollection(rulesXY.unsafePrefixes, unsafePrefixesXY)); Assert.assertTrue(CollectionUtils.isEqualCollection(rulesXY.unsafeSuffixes, unsafeSuffixesXY)); Assert.assertTrue(CollectionUtils.isEqualCollection(rulesXY.unsafeNames, unsafeNamesXY)); assertEqualMultimaps(rulesXY.transformationMatrix, transformationMatrixXY); /* given a mapping choice, prefer the safe character */ Assert.assertEquals((int) rulesXY.transformationMap.get(controlCharacterP), 65); Assert.assertEquals((int) rulesXY.transformationMap.get(normalCharacterP), 65); } /** * Test that transformation matrices are transitively closed upon combination. */ @Test public void testTransitiveTransformationClosure() { final SetMultimap<Integer, Integer> transformationMatrixX = HashMultimap.create(); final SetMultimap<Integer, Integer> transformationMatrixY = HashMultimap.create(); final SetMultimap<Integer, Integer> transformationMatrixZ = HashMultimap.create(); final SetMultimap<Integer, Integer> transformationMatrixXYZ = HashMultimap.create(); for (int codePoint = 0; codePoint < 0x100; codePoint++) { if (Character.getType(codePoint) == Character.CONTROL) { transformationMatrixXYZ.put(codePoint, 90); } } /* * 65 → 66 * ↘ ↓ ↘ * 67 68 * ↓ ↓ * 70 69 */ transformationMatrixX.put(65, 66); transformationMatrixX.put(65, 67); transformationMatrixY.put(66, 67); transformationMatrixY.put(66, 68); transformationMatrixZ.put(67, 70); transformationMatrixZ.put(68, 69); transformationMatrixXYZ.put(65, 69); transformationMatrixXYZ.put(65, 70); transformationMatrixXYZ.put(66, 69); transformationMatrixXYZ.put(66, 70); transformationMatrixXYZ.put(67, 70); transformationMatrixXYZ.put(68, 69); final Set<Character> safeCharacters = ImmutableSet.of('Z'); final FilePathRestrictions rulesX = new FilePathRestrictions(transformationMatrixX, null, null, null, safeCharacters); final FilePathRestrictions rulesY = new FilePathRestrictions(transformationMatrixY, null, null, null, safeCharacters); final FilePathRestrictions rulesZ = new FilePathRestrictions(transformationMatrixZ, null, null, null, safeCharacters); final FilePathRestrictions rulesXYZ = FilePathRestrictions.combineFilePathRestrictions(rulesX, rulesY, rulesZ); assertEqualMultimaps(rulesXYZ.transformationMatrix, transformationMatrixXYZ); final FilePathRestrictions rulesZYX = FilePathRestrictions.combineFilePathRestrictions(rulesZ, rulesY, rulesX); assertEqualMultimaps(rulesZYX.transformationMatrix, transformationMatrixXYZ); } /** * Test that cyclic transformation matrices do not cause an infinite loop. */ @Test(expectedExceptions=IllegalArgumentException.class) public void testCyclicTransformationCombination() { final SetMultimap<Integer, Integer> transformationMatrix = HashMultimap.create(); transformationMatrix.put(0, 0); final FilePathRestrictions rules = new FilePathRestrictions(transformationMatrix, null, null, null, ImmutableSet.of('A')); FilePathRestrictions.combineFilePathRestrictions(rules); } /** * Assert that collections contain exactly the same elements, regardless of ordering. * @param actual the actual elements * @param expected the expected elements */ private static <X> void assertEqualsNoOrder(Collection<X> actual, Collection<X> expected) { final HashSet<X> remaining = new HashSet<X>(actual); for (final X expectedItem : expected) { Assert.assertTrue(remaining.remove(expectedItem), "collections must contain the same elements"); } Assert.assertTrue(remaining.isEmpty(), "collections must contain the same elements"); } /** * Assert that file path restriction rules are identical in effect. * @param actual the actual rules * @param expected the expected rules */ private static void assertSameRules(FilePathRestrictions actual, FilePathRestrictions expected) { assertEqualsNoOrder(actual.transformationMatrix.entries(), expected.transformationMatrix.entries()); assertEqualsNoOrder(actual.unsafePrefixes, expected.unsafePrefixes); assertEqualsNoOrder(actual.unsafeSuffixes, expected.unsafeSuffixes); assertEqualsNoOrder(actual.unsafeNames, expected.unsafeNames); assertEqualsNoOrder(actual.safeCharacters, expected.safeCharacters); Assert.assertEquals(actual.safeCharacter, expected.safeCharacter); assertEqualsNoOrder(actual.transformationMap.entrySet(), expected.transformationMap.entrySet()); } /** * On Microsoft Windows, test that the applicable rules are those for Microsoft Windows. */ @Test @Assumption(methods = {"isWindows"}, methodClass = CurrentPlatform.class) public void testUnsafeCharacterUnsafetyWindows() { assertSameRules(FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.LOCAL_REQUIRED), FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.WINDOWS_REQUIRED)); assertSameRules(FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.LOCAL_OPTIONAL), FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.WINDOWS_OPTIONAL)); } /** * On Linux, test that the applicable rules are those for UNIX-like platforms. */ @Test @Assumption(methods = {"isLinux"}, methodClass = CurrentPlatform.class) public void testUnsafeCharacterUnsafetyLinux() { assertSameRules(FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.LOCAL_REQUIRED), FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.UNIX_REQUIRED)); assertSameRules(FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.LOCAL_OPTIONAL), FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.UNIX_OPTIONAL)); } /** * On Apple Mac OS X, test that the applicable rules are those for UNIX-like platforms. */ @Test @Assumption(methods = {"isMacOSX"}, methodClass = CurrentPlatform.class) public void testUnsafeCharacterUnsafetyMacOSX() { assertSameRules(FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.LOCAL_REQUIRED), FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.UNIX_REQUIRED)); assertSameRules(FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.LOCAL_OPTIONAL), FilePathRestrictionInstance.getFilePathRestrictions(FilePathRestrictionInstance.UNIX_OPTIONAL)); } }