package org.ovirt.engine.core.bll.storage.disk.image; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import java.io.IOException; import java.nio.charset.StandardCharsets; import org.apache.commons.codec.binary.Hex; import org.apache.commons.lang.StringUtils; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.ovirt.engine.core.common.businessentities.storage.Disk; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector; import org.ovirt.engine.core.di.InjectorRule; import org.ovirt.engine.core.utils.RandomUtils; import org.ovirt.engine.core.utils.RandomUtilsSeedingRule; @RunWith(MockitoJUnitRunner.class) public class MetadataDiskDescriptionHandlerTest { private static int DISK_ALIAS_MAX_LENGTH = 194; private DiskImage disk; private MetadataDiskDescriptionHandler metadataDiskDescriptionHandler; @ClassRule public static InjectorRule injectorRule = new InjectorRule(); @Mock private AuditLogDirector auditLogDirector; @Rule public RandomUtilsSeedingRule rusr = new RandomUtilsSeedingRule(); @Before public void setUp() { disk = new DiskImage(); metadataDiskDescriptionHandler = spy(MetadataDiskDescriptionHandler.getInstance()); doReturn(auditLogDirector).when(metadataDiskDescriptionHandler).getAuditLogDirector(); } @Test public void testEncodingDecoding() throws Exception { disk.setDiskAlias("DiskAlias"); disk.setDiskDescription("DiskDescription"); assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry("DiskAlias"), generateDiskDescriptionJsonEntry("DiskDescription")); assertDiskDescriptionDecoding(disk); } @Test public void encodeWithNullDiskDescription() throws Exception { disk.setDiskAlias("DiskAlias"); disk.setDiskDescription(null); assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry("DiskAlias"), generateDiskDescriptionJsonEntry("")); } @Test public void encodeDecodeWithEmptyDiskDescription() throws Exception { disk.setDiskAlias("DiskAlias"); disk.setDiskDescription(""); assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry("DiskAlias"), generateDiskDescriptionJsonEntry("")); assertDiskDescriptionDecoding(disk); } @Test public void encodeWhenAliasIsTruncated() throws IOException { String alias = generateRandomString(200); disk.setDiskAlias(alias); // Only 194 can be stored. disk.setDiskDescription("DiskDescription"); // Only first 194 bytes were stored from the alias. The description was lost. assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry(alias.substring(0, DISK_ALIAS_MAX_LENGTH))); } @Test public void encodeWhenDescriptionIsCompletelyTruncated() throws IOException { String alias = generateRandomString(DISK_ALIAS_MAX_LENGTH); disk.setDiskAlias(alias); // Exactly the limit. disk.setDiskDescription("DiskDescription"); // Alias was completely stored. The description was lost. assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry(alias)); } @Test public void encodeWhenDescriptionIsTruncated() throws IOException { disk.setDiskAlias("DiskAlias"); // diskAlias.length == 9 characters. String diskDescription = generateRandomString(170); disk.setDiskDescription(diskDescription); // Only 164 can be stored. // Only first 164 bytes were stored from the description. The alias was completely stored. assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry("DiskAlias"), generateDiskDescriptionJsonEntry(diskDescription.substring(0, 164))); } @Test public void encodeDecodeWithNonAsciiDiskAlias() throws Exception { disk.setDiskAlias("áéíñ"); disk.setDiskDescription("DiskDescription"); assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry(encodeString("áéíñ")), generateDiskDescriptionJsonEntry("DiskDescription"), generateEncodingJsonEntry(1)); assertDiskDescriptionDecoding(disk); } @Test public void encodeDecodeWithNonAsciiDiskDescription() throws Exception { disk.setDiskAlias("DiskAlias"); disk.setDiskDescription("áéíñ"); assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry("DiskAlias"), generateDiskDescriptionJsonEntry(encodeString("áéíñ")), generateEncodingJsonEntry(2)); assertDiskDescriptionDecoding(disk); } @Test public void encodeDecodeWithNonAsciiDiskAliasAndDescription() throws Exception { disk.setDiskAlias("áéíñáéíñ"); disk.setDiskDescription("áéíñ"); assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry(encodeString("áéíñáéíñ")), generateDiskDescriptionJsonEntry(encodeString("áéíñ")), generateEncodingJsonEntry(3)); assertDiskDescriptionDecoding(disk); } @Test public void encodeWhenNonAsciiAliasIsTruncated() throws IOException { String nonAsciiDiskAlias = "ááááááááááááááááááááááááááááááááááááááááááááááá"; // 47 characters // This string will be encoded to 188 bytes, but only 184 can be stored. disk.setDiskAlias(nonAsciiDiskAlias); disk.setDiskDescription("DiskDescription"); // Only first 46 characters (which were decoded to 184 bytes) from the alias were stored. assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry(encodeString(nonAsciiDiskAlias.substring(0, 46))), generateEncodingJsonEntry(1)); } @Test public void encodeWhenNonAsciiDiskDescriptionIsTruncated() throws IOException { disk.setDiskAlias("áéíñáé"); // 6 characters -> 24 bytes String nonAsciiDiskDescription = "ñññññññññññññññññññññññññññññññññññññññññññññññññññññññ"; // 55 characters // This string will be encoded to 220 bytes, but only 139 can be stored. disk.setDiskDescription(nonAsciiDiskDescription); // Only first 34 characters (which were decoded to 136 bytes) from the description were stored. assertDiskDescriptionMap(disk, generateDiskAliasJsonEntry(encodeString("áéíñáé")), generateDiskDescriptionJsonEntry(encodeString(nonAsciiDiskDescription.substring(0, 34))), generateEncodingJsonEntry(3)); } private static String generateDiskAliasJsonEntry(String diskAlias) { return MetadataDiskDescriptionHandler.getInstance().generateJsonField("DiskAlias", diskAlias); } private static String generateDiskDescriptionJsonEntry(String diskDescription) { return MetadataDiskDescriptionHandler.getInstance().generateJsonField("DiskDescription", diskDescription); } private static String generateEncodingJsonEntry(int encoding) { return MetadataDiskDescriptionHandler.getInstance().generateJsonField("Enc", String.valueOf(encoding)); } private static String generateRandomString(int stringLength) { return RandomUtils.instance().nextPropertyString(stringLength); } private static String encodeString(String str) { return Hex.encodeHexString(str.getBytes(StandardCharsets.UTF_16LE)); } private void assertDiskDescriptionMap(Disk disk, String... jsonEntries) throws IOException { assertEquals(String.format("{%s}", StringUtils.join(jsonEntries, ",", 0, jsonEntries.length)), metadataDiskDescriptionHandler.generateJsonDiskDescription(disk)); } private static void assertDiskDescriptionDecoding(Disk disk) throws Exception { Disk diskToEnrich = new DiskImage(); MetadataDiskDescriptionHandler.getInstance().enrichDiskByJsonDescription( MetadataDiskDescriptionHandler.getInstance().generateJsonDiskDescription(disk), diskToEnrich); assertEquals(diskToEnrich, disk); } }