From ecdc28d6198e942249273bb343dd6acf5ae8b547 Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 21 May 2014 13:38:03 +0000 Subject: [PATCH] splitting out web test case and unit test case --- islandora.info | 6 +- tests/datastream_validator_tests.test | 9 +- tests/datastream_validators.inc | 781 -------------------------- tests/islandora_web_test_case.inc | 604 -------------------- 4 files changed, 8 insertions(+), 1392 deletions(-) delete mode 100644 tests/datastream_validators.inc delete mode 100644 tests/islandora_web_test_case.inc diff --git a/islandora.info b/islandora.info index e06d0de9..e19f4937 100644 --- a/islandora.info +++ b/islandora.info @@ -13,7 +13,10 @@ files[] = includes/dublin_core.inc files[] = includes/tuque.inc files[] = includes/tuque_wrapper.inc files[] = includes/object.entity_controller.inc -files[] = tests/islandora_web_test_case.inc +files[] = tests/includes/datastream_validators.inc +files[] = tests/includes/islandora_web_test_case.inc +files[] = tests/includes/islandora_unit_test_case.inc +files[] = tests/includes/utilities.inc files[] = tests/authtokens.test files[] = tests/hooks.test files[] = tests/ingest.test @@ -22,6 +25,5 @@ files[] = tests/islandora_manage_permissions.test files[] = tests/datastream_versions.test files[] = tests/datastream_cache.test files[] = tests/derivatives.test -files[] = tests/islandora_manage_temp_file.test files[] = tests/datastream_validator_tests.test php = 5.3 diff --git a/tests/datastream_validator_tests.test b/tests/datastream_validator_tests.test index 79e1df7f..40615ae4 100644 --- a/tests/datastream_validator_tests.test +++ b/tests/datastream_validator_tests.test @@ -4,8 +4,6 @@ * Tests for things that test tests. Madness. */ -include 'datastream_validators.inc'; - /** * A test DatastreamValidator for the DatastreamValidatorResultTestCase. */ @@ -62,7 +60,7 @@ class DatastreamValidatorResultTestCase extends IslandoraWebTestCase { * Generates a generic DatastreamValidatorResult and grabs its properties. */ public function testDatastreamValidatorResult() { - $result = new DatastreamValidatorResult(TRUE, 'true', array()); + $result = new IslandoraTestUtilityResult(TRUE, 'true', array()); $this->assertEqual($result->getMessage(), 'true', "Result message generated correctly.", 'Islandora'); $this->assertEqual($result->getType(), TRUE, "Result type generated correctly.", 'Islandora'); $this->assertEqual($result->getCaller(), array(), "Result caller generated correctly.", 'Islandora'); @@ -111,10 +109,11 @@ class DatastreamValidatorResultTestCase extends IslandoraWebTestCase { $this->assertTrue(substr($first_caller['file'], -48) === substr($second_caller['file'], -48), "Fail caller file matches the pass caller file.", 'Islandora'); $this->assertTrue($first_caller['function'] === 'TestDatastreamValidator->assertSomethingSuccessfully()', "Correct pass caller function returned (actual: {$first_caller['function']}; expected: TestDatastreamValidator->assertSomethingSuccessfully()).", 'Islandora'); $this->assertTrue($second_caller['function'] === 'TestDatastreamValidator->assertSomethingFailed()', "Correct fail caller function returned (actual: {$second_caller['function']}; expected: TestDatastreamValidator->assertSomethingFailed()).", 'Islandora'); - $this->assertTrue($first_caller['line'] === 18, "Correct pass line number returned (actual: {$first_caller['line']}; expected: 9).", 'Islandora'); - $this->assertTrue($second_caller['line'] === 25, "Correct fail line number returned (actual: {$second_caller['line']}; expected: 13).", 'Islandora'); + $this->assertTrue($first_caller['line'] === 16, "Correct pass line number returned (actual: {$first_caller['line']}; expected: 16).", 'Islandora'); + $this->assertTrue($second_caller['line'] === 23, "Correct fail line number returned (actual: {$second_caller['line']}; expected: 23).", 'Islandora'); } } + } /** diff --git a/tests/datastream_validators.inc b/tests/datastream_validators.inc deleted file mode 100644 index 151ec676..00000000 --- a/tests/datastream_validators.inc +++ /dev/null @@ -1,781 +0,0 @@ -message = $message; - $this->caller = $caller; - $this->type = $type; - } - - /** - * Get the message for this result. - * - * @return string - * The message for this result. - */ - public function getMessage() { - return $this->message; - } - - /** - * Get the caller for this result. - * - * @return string - * The caller for this result. - */ - public function getCaller() { - return $this->caller; - } - - /** - * Get the type of result. - * - * @return bool - * The type of pass (TRUE for pass, FALSE for fail). - */ - public function getType() { - return $this->type; - } -} - -/** - * Abstraction for datastream validators. - * - * Classes extended from DatastreamValidator don't require much to be useful. - * They accept an IslandoraFedoraObject and a DSID to perform assertions on; - * all you have to do is place a series of functions inside the extended class - * using the naming convention assertThing(); each of these functions should - * ideally assert one thing and one thing only (for simplicity's sake), and - * should generate either a pass or a fail message by calling addPass() or - * addFail(). That's it, really; they don't have to return any values, as - * addPass() and addFail() just add messages to the overall pass/fail array. - * - * As long as you use those rules and naming conventions, all the magic is done - * when you instantiate the new datastream validator object. - * - * The IslandoraWebTestCase::assertDatastreams() function accepts paired DSIDs - * and datastream validator names in order to do the rest of the work. It grabs - * all the test results using getPasses() and getFails() and transforms those - * into something that DrupalWebTestCase can use. - */ -abstract class DatastreamValidator { - - /** - * The IslandoraFedoraObject containing the datastream to test. - * - * @var IslandoraFedoraObject - */ - public $object; - - /** - * The DSID of the string to test. - * - * @var string - */ - public $datastream; - - /** - * The content of the datastream. - * - * @var string[] - */ - public $datastreamContent; - - /** - * An array of DatastreamValidatorResults. - * - * These should be generated using $this->addResult. - * - * @var DatastreamValidatorResult[] - */ - public $results = array(); - - /** - * An array of additional required parameters. - * - * @var array - */ - public $params = array(); - - /** - * Constructs a DatastreamValidator. - * - * @param IslandoraFedoraObject $object - * The object to grab the datastream from. - * @param string $datastream - * The DSID of the datastream itself. - * @param array $params - * An extra array of parameters the validator might need. - */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { - $this->object = $object; - $this->datastream = $datastream; - $this->params = $params; - if ($object[$datastream]) { - $this->datastreamContent = $object[$datastream]->content; - } - else { - drupal_set_message(t("Error grabbing content from datastream %datastream in object %id", array( - '%datastream' => $datastream, - '%id' => $object->id, - )), 'error'); - } - } - - /** - * Helper function to run all the validators in a class. - * - * On DatastreamValidator::__construct(), this looks for any functions - * within the class beginning in "assert" and runs them. In all current cases - * (and realistically in all future cases), this adds one or more passes or - * fails to $this->passes and/or $this->fails. - */ - public function runValidators() { - $methods = get_class_methods($this); - foreach ($methods as $method) { - if (substr($method, 0, 6) === 'assert') { - $this->$method(); - } - } - } - - /** - * Returns an array of DatastreamValidatorResults. - * - * @return DatastreamValidatorResult[] - * The results. - */ - public function getResults() { - if (empty($this->results)) { - $this->runValidators(); - } - return $this->results; - } - - /** - * Adds a result to $this->results. - * - * @param bool $type - * The type of result (TRUE for pass, FALSE for fail). - * @param string $message - * The message to put in the result. - */ - public function addResult($type, $message) { - $result = new DatastreamValidatorResult($type, $message, $this->getAssertionCall()); - $this->results[] = $result; - } - - /** - * Cycles through backtrace until the first non-assertion method is found. - * - * This is a manipulated version of DrupalWebTestCase::getAssertionCall(). - * We use it here so that we can pass back assertion calls from - * DatastreamValidator assertions instead of less useful TestCase functions. - * - * @return array - * Array representing the true caller. - */ - protected function getAssertionCall() { - $backtrace = debug_backtrace(); - - // While the current caller's function starts with 'assert', and another one - // exists after this function, keep poppin' em off. - while (substr($backtrace[1]['function'], 0, 6) !== 'assert' && isset($backtrace[2])) { - array_shift($backtrace); - } - - return _drupal_get_last_caller($backtrace); - } - -} - -/** - * Asserts that an object's given datastreams are common-type image files. - * - * Uses PHPGD to run the assertion check. This means that only certain kinds - * of image files can be checked. Please check the documentation for the PHPGD - * imagecreatefromstring() function to determine what filetypes are valid. - */ -class ImageDatastreamValidator extends DatastreamValidator { - - /** - * Asserts the validity of an image using PHPGD. - */ - protected function assertImageGeneration() { - $assertion = imagecreatefromstring($this->datastreamContent) !== FALSE; - $pass = "Image datastream {$this->datastream} is valid."; - $fail = "Image datastream {$this->datastream} is either invalid or corrupt."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - -} - -/** - * Asserts the validity of any .tif/.tiff datastream. - */ -class TIFFDatastreamValidator extends DatastreamValidator { - - /** - * Asserts that the TIFF contains an appropriate header. - */ - public function assertTIFFHeaderHex() { - $datastream_header_hex = self::getTIFFHeaderHex(); - if ($datastream_header_hex == "49492a00") { - // In this case, the ingested TIFF is designated as using the "Intel - // byte-order" (i.e. little-endian) by starting with the characters "II" - // (repeated so that byte order does not yet need to be significant). - // The number that follows is '42' in little-endian hex, a number of - // 'deep philosophical significance' to the TIFF format creators.' - $this->addResult(TRUE, "{$this->datastream} datastream asserts that it is a valid Intel-byte-orderded TIF/TIFF file."); - } - elseif ($datastream_header_hex == "4d4d002a") { - // In this case, the ingested TIFF is designated as using the "Motorola - // byte-order" (i.e. big-endian) by starting with the characters "MM" - // instead. 42 follows once again, this time in big-endian hex. - $this->addResult(TRUE, "{$this->datastream} datastream asserts that it is a valid Motorola-byte-ordered TIF/TIFF file."); - } - else { - $this->addResult(FALSE, "{$this->datastream} datastream does not assert that it is a valid TIF/TIFF file."); - } - } - - /** - * Grabs the first 8 characters from the TIFF datastream's hex. - * - * @return string - * The ... thing I just wrote up there. - */ - protected function getTIFFHeaderHex() { - return substr(bin2hex($this->datastreamContent), 0, 8); - } - -} - -/** - * Asserts the validity of a JP2 datastream. - */ -class JP2DatastreamValidator extends DatastreamValidator { - - /** - * Asserts the hex values at the head of the JP2 file. - * - * JP2 files begin with an offset header at the second 32-bit integer, - * 0x6A502020. This header is in all .jp2s, and we check for it here. - */ - protected function assertJP2Header() { - $assertion = substr(bin2hex($this->datastreamContent), 8, 8) == '6a502020'; - $pass = "Datastream {$this->datastream} contains the appropriate JP2 header."; - $fail = "Datastream {$this->datastream} does not contain the appropriate JP2 header."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts the marker at the end of the JP2 file. - * - * JP2 files have their codestream capped with a marker, 0xFFD9. We're just - * checking for it here to see if the .jp2 encoder finished okay. - */ - protected function assertJP2Marker() { - $assertion = substr(bin2hex($this->datastreamContent), strlen(bin2hex($this->datastreamContent)) - 4, 4) == 'ffd9'; - $pass = "Datastream {$this->datastream} contains the appropriate JP2 ending marker."; - $fail = "Datastream {$this->datastream} does not contain the appropriate JP2 ending marker. If this is the only JP2 validator that failed, it is likely that derivative generation was interrupted."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} - -/** - * Asserts the validity of a PDF datastream. - */ -class PDFDatastreamValidator extends DatastreamValidator { - - /** - * Validates the PDF signature. - */ - protected function assertPDFSignature() { - $assertion = substr($this->datastreamContent, 0, 5) == '%PDF-'; - $pdf_version = substr($this->datastreamContent, 5, 3); - $pass = "{$this->datastream} datastream asserts that it is a valid PDF file using PDF version {$pdf_version}"; - $fail = "{$this->datastream} datastream binary header appears to be corrupt and missing a valid PDF signature."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Counts the number of signatures in this PDF file and asserts there are any. - */ - protected function assertPDFStreamCount() { - $pdf_stream_count = substr_count(bin2hex($this->datastreamContent), '0a73747265616d0a'); - $assertion = $pdf_stream_count !== 0; - $pass = "{$this->datastream} datastream reports the existence of {$pdf_stream_count} PDF streams. Note that an extremely low number could still indicate corruption."; - $fail = "{$this->datastream} datastream contains zero PDF streams, and is likely not a PDF file."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Validates the PDF closing tag. - * - * @return bool - * TRUE if it was present; FALSE otherwise. - */ - protected function assertPDFClosingTag() { - $assertion = strpos(bin2hex($this->datastreamContent), '0a2525454f460a') == TRUE; - $pass = "{$this->datastream} datastream reports the existence of the closing 'EOF' tag required at the end of PDFs"; - $fail = "{$this->datastream} datastream does not contain the closing 'EOF' tag. If this is the only PDF validation that failed, it is likely that derivative generation was interrupted."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} - -/** - * Validates the number of times a string occurs in a datastream. - * - * Requires $this->params to be set to an array containing two keys - the first - * is the string we're looking to find in the datastream, and the second is an - * integer representing the number of times it should appear in the datastream. - */ -class TextDatastreamValidator extends DatastreamValidator { - /** - * Constructor override; blow up if we don't have our two values. - */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { - if (count($params) < 2) { - throw new InvalidArgumentException('$params must contain at least two values to instantiate a TextDatastreamValidator.'); - } - parent::__construct($object, $datastream, $params); - } - - /** - * Asserts that the string given appears the correct number of times. - */ - protected function assertTextStringCount() { - $string_count = self::getTextStringCount(); - list($string, $expected) = $this->params; - $assertion = $string_count === $expected; - $this->addResult($assertion, "{$this->datastream} datastream contains the word(s) '{$string}' repeated {$string_count} time(s) (expected: {$expected})."); - } - - /** - * The number of times key [0] in $this->params appears in the datastream. - * - * @return int - * That count I just mentioned up there. - */ - protected function getTextStringCount() { - return substr_count($this->datastreamContent, $this->params[0]); - } -} - -/** - * Asserts the validity a WAV datastream. - * - * WAV files contain a rigidly detailed header that contains all sorts of fun - * information we can use to validate things against other things. So, we check - * rigorously that the header contains properly constructed data by looking to - * see if certain values are at their expected byte offset. We also compare - * declared chunk sizes against actual sizes. If any of these are off, WAV - * players will fail to function. - */ -class WAVDatastreamValidator extends DatastreamValidator { - - /** - * We need a special constructor here to get the hex datastream content. - * - * @param IslandoraFedoraObject $object - * The object to grab the datastream from. - * @param string $datastream - * The DSID of the datastream itself. - * @param array $params - * An extra array of parameters the validator might need. - */ - public function __construct(IslandoraFedoraObject $object, $datastream, array $params = array()) { - parent::__construct($object, $datastream, $params); - $this->datastreamContent = bin2hex($this->datastreamContent); - } - - /** - * Asserts that the datastream contains a valid WAV signature. - */ - protected function assertWAVSignature() { - $signatures = str_split(substr($this->datastreamContent, 0, 24), 8); - $assertion = $signatures[0] == '52494646' && $signatures[2] == '57415645'; - $pass = "Header of the {$this->datastream} datastream contains a valid file signature."; - $fail = "Header of the {$this->datastream} datastream contains corrupt file signature."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the chunksize in the header is correct. - */ - protected function assertWAVChunkSize() { - $assertion = islandora_hex2int(substr($this->datastreamContent, 8, 8)) === 36 + self::getDataSubChunkSize(); - $pass = "{$this->datastream} datastream chunksize in WAV header is correct"; - $fail = "{$this->datastream} datastream chunksize in WAV header does not match actual chunksize."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the datastream contains a 'fmt' subchunk. - */ - protected function assertWAVFmtSubChunk() { - $assertion = substr($this->datastreamContent, 24, 8) === '666d7420'; - $pass = "{$this->datastream} datastream contains a 'fmt' subchunk."; - $fail = "{$this->datastream} datastream is missing the required 'fmt' subchunk."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the byterate reported by the WAV header is valid. - */ - protected function assertWAVByteRate() { - $wav_samplerate = islandora_hex2int(substr($this->datastreamContent, 48, 8)); - $assertion = islandora_hex2int(substr($this->datastreamContent, 56, 8)) === $wav_samplerate * self::getNumChannels() * self::getBytesPerSample(); - $pass = "{$this->datastream} datastream byterate in the WAV header is correct."; - $fail = "{$this->datastream} datastream byterate in the WAV header does not match actual calculated byterate."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the block alignment is correct. - */ - protected function assertWAVBlockAlignment() { - $assertion = islandora_hex2int(substr($this->datastreamContent, 64, 4)) === self::getNumChannels() * self::getBytesPerSample(); - $pass = "{$this->datastream} datastream block alignment is set correctly."; - $fail = "{$this->datastream} datastream block alignment is off."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts the existence of a 'data' subchunk. - * - * Also asserts that the subchunk size is correct. - */ - protected function assertWAVDataSubChunk() { - if (substr($this->datastreamContent, 72, 8) !== '64617461') { - $this->addResult(FALSE, "{$this->datastream} datastream is missing the 'data' subchunk."); - return; - } - else { - $this->addResult(TRUE, "{$this->datastream} datastream contains 'data' subchunk."); - $wav_numsamples = strlen(substr($this->datastreamContent, 88)) / self::getNumChannels() / self::getBytesPerSample() / 2; - $assertion = self::getDataSubChunkSize() === $wav_numsamples * self::getNumChannels() * self::getBytesPerSample(); - $pass = "{$this->datastream} datastream 'data' chunk is the correct size."; - $fail = "{$this->datastream} datastream 'data' chunk is sized incorrectly."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - } - - /** - * Gets the number of channels reported by the WAV header. - * - * @return int - * The number of channels reported by the datastream header. - */ - protected function getNumChannels() { - return islandora_hex2int(substr($this->datastreamContent, 44, 4)); - } - - /** - * Gets the reported number of byte rates per sample. - * - * @return int - * The number of bytes per sample reported by the datastream header. - */ - protected function getBytesPerSample() { - return islandora_hex2int(substr($this->datastreamContent, 68, 4)) / 8; - } - - /** - * Gets the size of the 'data' subchunk. - * - * @return int - * The size of the 'data' subchunk. - */ - protected function getDataSubChunkSize() { - return islandora_hex2int(substr($this->datastreamContent, 80, 8)); - } -} - -/** - * Asserts the validity of any .mp3 datastream. - * - * Our default setup tries to create an MP3 using VBR, but we do some extra - * checks in case someone turns that off. If the header contains the characters - * 'Xing', it is flagged as VBR, and we can do an in-depth check on each of the - * VBR settings. Otherwise, we look for the basic MP3 signature 'fffa' or 'fffb' - * at the start of the binary. - */ -class MP3DatastreamValidator extends DatastreamValidator { - - /** - * Asserts the validity of the MP3. - * - * The MP3 file format is a bit of a mess; the entire makeup of the file - * depends on whether it uses variable bit rate or static bit rate. So, I'm - * breaking my own rules here and using a single assert function so that I - * can handle the weird logic. - */ - protected function assertValidMP3() { - $this->datastreamContent = bin2hex($this->datastreamContent); - - // If it's not a VBR MP3, we don't have to check much, so let's get that - // out of the way first before we go doing a bunch of potentially pointless - // math. Check to see if the VBR flag (58696e67) isn't there. - if (strpos($this->datastreamContent, '58696e67') == FALSE && substr($this->datastreamContent, 0, 4) == 'fffa') { - $this->addResult(TRUE, "{$this->datastream} datastream is encoded as a valid MPEG-1 Layer 3 file with CRC protection"); - return; - } - if (strpos($this->datastreamContent, '58696e67') == FALSE && substr($this->datastreamContent, 0, 4) == 'fffb') { - $this->addResult(TRUE, "{$this->datastream} datastream is encoded as a valid unprotected MPEG-1 Layer 3 file"); - return; - } - - // And what if the flag IS set? - if (strpos($this->datastreamContent, '58696e67')) { - // Check the field flags. VBR-formatted MP3 files contain a 32-bit - // integer (stored as $mp3_flag_value) that is a combination of four - // bits, each one indicating the on-off status of a VBR setting, via - // logical OR. Rather than disassembling this value into individual - // bits, we use the algorithm "if (binary_total+bit_value*2)/bit_value*2 - // is greater than or equal to bit_value, that bit is turned on" to find - // the status of each bit, so we know whether to offset the rest. - $mp3_field_offset = array(0, 0, 0); - $mp3_vbrheader = substr($this->datastreamContent, strpos($this->datastreamContent, '58696e67'), 240); - $mp3_flag_value = hexdec(substr($mp3_vbrheader, 8, 8)); - - // We can't use the first flag, but we still need to offset the rest. - if (($mp3_flag_value + 1) % 2 == 0) { - $mp3_field_offset[0] += 8; - $mp3_field_offset[1] += 8; - $mp3_field_offset[2] += 8; - } - - // The second flag leads us to filesize data, which we can verify. - if (($mp3_flag_value + 4) % 4 > 1) { - $mp3_field_bytes = hexdec(substr($mp3_vbrheader, $mp3_field_offset[0] + 16, 8)); - $mp3_size = strlen($this->datastreamContent) / 2; - $assertion = $mp3_size == $mp3_field_bytes; - $pass = "{$this->datastream} datastream reported filesize of {$mp3_size} bytes matches size field value of {$mp3_field_bytes}"; - $fail = "{$this->datastream} datastream reported filesize of {$mp3_size} bytes does not match size field value of {$mp3_field_bytes}"; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - $mp3_field_offset[1] += 8; - $mp3_field_offset[2] += 8; - } - - // We can't use the third flag for anything, but we still have to offset. - if (($mp3_flag_value + 8) % 8 > 3) { - $mp3_field_offset[2] += 200; - } - - // The fourth flag leads us to VBR quality data, which we can validate. - if ($mp3_flag_value > 7) { - $mp3_field_quality = hexdec(substr($mp3_vbrheader, $mp3_field_offset[2] + 16, 8)); - $assertion = $mp3_field_quality <= 100 && $mp3_field_quality >= 0; - $pass = "{$this->datastream} datastream reports valid VBR quality of {$mp3_field_quality} (expected: between 0-100)"; - $fail = "{$this->datastream} datastream reports invalid VBR quality of {$mp3_field_quality} (expected: between 0-100)"; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - } - - // If none of that works out, fail. - else { - $this->addResult(FALSE, "{$this->datastream} datastream is corrupt and does not identify as a valid MP3."); - } - } - -} - -/** - * Attempts to validate an .mp4 datastream. - * - * MP4 files are a subset of the ISO file format specification, and as such need - * to contain a 64-bit declaration of type within the first eight eight bytes of - * the file. This declaration is comprised of the characters 'ftyp', followed by - * a four-character filetype code. Here, we look for 'ftyp', and then pass the - * filetype code to the test message. - */ -class MP4DatastreamValidator extends DatastreamValidator { - - /** - * Asserts that the datastream is ISO-formatted video. - */ - protected function assertISOVideo() { - $mp4_ftyp = substr(strpos($this->datastreamContent, 'ftyp'), 4, 4); - $assertion = strpos($this->datastreamContent, 'ftyp') !== 0; - $pass = "{$this->datastream} datastream asserts that it is a valid ISO-formatted video file using ftyp {$mp4_ftyp}"; - $fail = "{$this->datastream} datastream is not a valid ISO-formatted video"; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} - -/** - * Attempts to validate an .ogg/ogv datastream using Vorbis and Theora encoding. - * - * OGG files are made up of several 'pages' of OGG data, each prefaced with an - * OGG marker - the letters 'OggS'. The file header also contains information on - * what encoders were used to create the file. Here, we're looking for at least - * one OGG page, and confirming that the file asserts the Theora and Vorbis - * codecs were used to create the file. - */ -class OGGDatastreamValidator extends DatastreamValidator { - - /** - * Asserts that the datastream contains ogg pages. - */ - protected function assertOGGPages() { - $ogg_pages = substr_count($this->datastreamContent, 'OggS'); - $assertion = $ogg_pages !== 0; - $pass = "{$this->datastream} datastream asserts that it contains {$ogg_pages} Ogg pages (even a very small file should contain several)."; - $fail = "{$this->datastream} datastream contains no Ogg pages."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the datastream contains Theora-encoded video. - */ - protected function assertTheoraVideo() { - $assertion = substr_count($this->datastreamContent, 'theora') !== 0; - $pass = "{$this->datastream} datastream asserts that it contains Theora-encoded video data."; - $fail = "{$this->datastream} datastream contains no marker indicating the presence of Theora-encoded video data."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the datastream contains Vorbis-encoded audio. - */ - protected function assertVorbisAudio() { - $assertion = substr_count($this->datastreamContent, 'vorbis') !== 0; - $pass = "{$this->datastream} datastream asserts that it contains Vorbis-encoded audio data"; - $fail = "{$this->datastream} datastream contains no marker indicating the presence of Vorbis-encoded audio data."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} - -/** - * Attempts to validate an .mkv datastream. - * - * There's not much we can do to check an MKV file, since the format is really, - * really loose. We do know a couple of things though - first, since MKV is an - * EBML format, the first four characters will always be the same. Since they're - * non-standard characters, we're looking at their hex values instead. And - * second, we know that the file will contain the declaration 'matroska' soon - * after. - */ -class MKVDatastreamValidator extends DatastreamValidator { - - /** - * Asserts that the datastream is an EBML-format file. - */ - protected function assertEBMLFormat() { - $assertion = substr(bin2hex($this->datastreamContent), 0, 8) == '1a45dfa3'; - $pass = "{$this->datastream} datastream asserts that it is an EBML-formatted file"; - $fail = "{$this->datastream} datastream is not an EBML-formatted file."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } - - /** - * Asserts that the datastream contains a matroska marker. - */ - protected function assertMatroskaMarker() { - $assertion = substr_count($this->datastreamContent, 'matroska') == 1; - $pass = "{$this->datastream} datastream asserts that its EBML DocType is Matroska"; - $fail = "{$this->datastream} datastream does not contain a Matroska EBML DocType marker."; - $message = $assertion ? $pass : $fail; - $this->addResult($assertion, $message); - } -} diff --git a/tests/islandora_web_test_case.inc b/tests/islandora_web_test_case.inc deleted file mode 100644 index 8a1e5ce8..00000000 --- a/tests/islandora_web_test_case.inc +++ /dev/null @@ -1,604 +0,0 @@ -configuration = $this->getTestConfiguration(); - if ($this->configuration['use_drupal_filter']) { - $this->backUpDrupalFilter(); - $this->setUpDrupalFilter(); - } - $this->admin = $this->createAdminUser(); - } - - /** - * Parses and returns the settings from the test configuration file. - * - * If no install specific test_config.ini file is found, it will use the - * assumed default configs found in default.test_config.ini. - * - * @return array - * The test configuration. - * - * @see parse_ini_file() - */ - protected function getTestConfiguration() { - $path = drupal_get_path('module', 'islandora'); - if (file_exists("$path/tests/test_config.ini")) { - $this->pass('Using custom test configuration.'); - return parse_ini_file("$path/tests/test_config.ini"); - } - elseif (file_exists("$path/tests/default.test_config.ini")) { - $this->pass('Using default test configuration.'); - return parse_ini_file("$path/tests/default.test_config.ini"); - } - throw new Exception('Required default.test_config.ini/test_config.ini file not found'); - } - - /** - * Stores the content of the Drupal Filter for later restoration. - */ - protected function backUpDrupalFilter() { - if (file_exists($this->configuration['drupal_filter_file'])) { - $this->originalDrupalFilterContent = file_get_contents($this->configuration['drupal_filter_file']); - } - else { - throw new Exception('Failed to find the required Drupal Filter configuration file.'); - } - } - - /** - * Sets up a drupal filter that can read for the tests users table. - */ - protected function setUpDrupalFilter() { - $connection_info = Database::getConnectionInfo('default'); - $drupal_filter_dom = new DomDocument(); - $drupal_filter_dom->loadXML($this->originalDrupalFilterContent); - $server = $connection_info['default']['host']; - $dbname = $connection_info['default']['database']; - $user = $connection_info['default']['username']; - $password = $connection_info['default']['password']; - $port = $connection_info['default']['port'] ? $connection_info['default']['port'] : '3306'; - $prefix = $connection_info['default']['prefix']['default']; - $filter_drupal_connection_node = $drupal_filter_dom->getElementsByTagName('FilterDrupal_Connection')->item(0); - $first_connection_node = $drupal_filter_dom->getElementsByTagName('connection')->item(0); - $connection_node = $filter_drupal_connection_node->insertBefore($drupal_filter_dom->createElement('connection'), $first_connection_node); - $connection_node->setAttributeNode(new DOMAttr('server', $server)); - $connection_node->setAttributeNode(new DOMAttr('dbname', $dbname)); - $connection_node->setAttributeNode(new DOMAttr('user', $user)); - $connection_node->setAttributeNode(new DOMAttr('password', $password)); - $connection_node->setAttributeNode(new DOMAttr('port', $port)); - $sql_node = $connection_node->appendChild(new DOMElement('sql')); - $sql_node->appendChild($drupal_filter_dom->createTextNode("SELECT DISTINCT u.uid AS userid, u.name AS Name, u.pass AS Pass, r.name AS Role FROM ({$prefix}users u LEFT JOIN {$prefix}users_roles ON u.uid={$prefix}users_roles.uid) LEFT JOIN {$prefix}role r ON r.rid={$prefix}users_roles.rid WHERE u.name=? AND u.pass=?;")); - file_put_contents($this->configuration['drupal_filter_file'], $drupal_filter_dom->saveXML()); - } - - /** - * Creates the a full fedora admin user with a repository connection. - */ - protected function createAdminUser() { - $roles = user_roles(); - $index = array_search('administrator', $roles); - $user = $this->drupalCreateUser(); - $user->roles[$index] = 'administrator'; - $user->name = $this->configuration['admin_user']; - $user->pass = $this->configuration['admin_pass']; - $user = user_save($user); - $url = variable_get('islandora_base_url', $this->configuration['fedora_url']); - $connection = islandora_get_tuque_connection($user, $url); - $user->repository = $connection->repository; - return $user; - } - - /** - * Logs in the given user, handles the special case where the user is admin. - * - * @see DrupalWebTestCase::drupalLogin() - */ - protected function drupalLogin(stdClass $account) { - if ($account->uid == $this->admin->uid) { - // Create password for Drupal. - $edit = array('pass' => user_password()); - $account = user_save($account, $edit); - // Raw password is used to login. - $account->pass_raw = $edit['pass']; - // We must login before changing the password for fedora. - parent::drupalLogin($account); - $account->name = $this->configuration['admin_user']; - $account->pass = $this->configuration['admin_pass']; - // Save the fedora admin credentials for later GET/POST requests. - $account = user_save($account); - } - else { - parent::drupalLogin($account); - $this->users[] = $account->name; - } - } - - - /** - * Stores the content of the Drupal Filter for later restoration. - */ - protected function restoreDrupalFilter() { - $file = $this->configuration['drupal_filter_file']; - if (isset($this->originalDrupalFilterContent)) { - file_put_contents($file, $this->originalDrupalFilterContent); - } - elseif (file_exists($file)) { - // Remove if there was never an original. - drupal_unlink($file); - } - } - - /** - * Restores the original Drupal filter, frees any allocated resources. - * - * To safeguard against leaving test objects in the repository, tearDown() - * calls deleteUserCreatedObjects() every time by default. This feature can be - * toggled by setting $this->deleteObjectsOnTeardown to TRUE or FALSE. - * - * @see DrupalWebTestCase::tearDown() - */ - public function tearDown() { - if ($this->deleteObjectsOnTeardown) { - foreach ($this->users as $user) { - $this->deleteUserCreatedObjects($user); - } - } - if ($this->configuration['use_drupal_filter']) { - $this->restoreDrupalFilter(); - } - unset($this->admin); - unset($this->configuration); - parent::tearDown(); - } - - /** - * Asserts that the given datastreams exist correctly on the object. - * - * @param AbstractObject $object - * The PID of the object - * @param array $datastreams - * An array of strings containing datastream names - * - * @return bool - * TRUE on success, FALSE on fail. - */ - public function assertDatastreams($object, array $datastreams) { - if (!is_object($object)) { - $this->fail("Failed. Object passed in is invalid.", 'Islandora'); - } - else { - $missing_datastreams = array_diff_key(array_flip($datastreams), $this->admin->repository->api->a->listDatastreams($object->id)); - - if (!empty($missing_datastreams)) { - $this->fail("Failed to find datastream(s) " . implode(', ', array_flip($missing_datastreams)) . " in object {$object->id}."); - return FALSE; - } - - $this->pass("Found required datastream(s) in object {$object->id}"); - return TRUE; - } - } - - /** - * Attempts to validate an array of datastreams, generally via binary checks. - * - * Datastream validation classes exist in, and can be added to, the file - * 'datastream_validators.inc', which is found in this folder. Datastream - * validator classes use the naming convention 'PrefixDatastreamValidator', - * and that 'Prefix' is what this function uses to determine what class to - * instantiate. - * - * $param IslandoraFedoraObject $object - * The object to load datastreams from. - * $param array $datastreams - * An array of arrays that pair DSIDs, DatastreamValidator class prefixes, - * and optional params. You can check some of the existing implementations - * for examples. - */ - public function validateDatastreams($object, array $datastreams) { - - if (!is_object($object)) { - $this->fail("Datastream validation failed; Object passed in is invalid.", 'Islandora'); - return; - } - - module_load_include('inc', 'islandora', 'tests/datastream_validators'); - - foreach ($datastreams as $datastream) { - // Let's give them conventional names. - $dsid = $datastream[0]; - $prefix = $datastream[1]; - $params = array(); - if (isset($datastream[2])) { - $params = $datastream[2]; - } - - // Legacy tests were created before the CamelCase conventions of the class - // system now in place. So, we need to automagically seek out prefixes - // that start with a lower-case letter and convert them to the proper - // format (rather than fixing every single legacy test). - if (ctype_lower(substr($prefix, 0, 1))) { - // Handle the case where the prefix is "image". - if ($prefix === 'image') { - $prefix = 'Image'; - } - // Handle the case where the prefix is "text". - elseif ($prefix === 'text') { - $prefix = 'Text'; - } - // All other cases involve just converting everything to caps. - else { - $prefix = strtoupper($prefix); - } - } - - // Instantiate the appropriate class, and grab the results. - $class_name = "{$prefix}DatastreamValidator"; - if (class_exists($class_name)) { - $validator = new $class_name($object, $dsid, $params); - foreach ($validator->getResults() as $result) { - $this->assert($result->getType(), $result->getMessage(), 'Islandora', $result->getCaller()); - } - } - else { - $this->fail("No DatastreamValidator class was found with the name '$class_name'; are you sure the prefix given to IslandoraWebTestCase->validateDatastreams() was entered correctly, or that such a validator exists?", 'Islandora'); - } - } - } - - /** - * Gets a tuque object from a path. - * - * @param string $path - * A full or partial path to an islandora object. - * - * @return AbstractObject - * The pid of the object or FALSE if a PID is not found. - */ - public function getObjectFromPath($path) { - $path_parts = explode('/', $path); - $array_length = count($path_parts); - for ($i = 0; $i < $array_length; $i++) { - if ($path_parts[$i] == 'islandora' && isset($path_parts[$i + 1]) && $path_parts[$i + 1] == 'object') { - if (isset($path_parts[$i + 2])) { - return islandora_object_load(urldecode($path_parts[$i + 2])); - } - } - } - $this->fail("Failed to parse path: $path."); - return FALSE; - } - - /** - * Deletes an object using the PID. This does the deletion using the UI. - * - * @param string $pid - * The PID of the collection to be deleted - * @param string $button - * The label of the first 'Delete' button - * @param bool $safety - * If TRUE, this will only delete objects owned by users in $this->users. - */ - public function deleteObject($pid, $button = NULL, $safety = TRUE) { - $object = islandora_object_load($pid); - if (!$safety || in_array($object->owner, $this->users)) { - $path = "islandora/object/$pid/manage/properties"; - if (isset($button)) { - $this->drupalPost($path, array(), $button); - } - else { - $object = islandora_object_load($pid); - $this->drupalPost($path, array(), "Permanently remove '{$object->label}' from repository"); - } - $this->drupalPost($this->url, array(), t('Delete')); - - $this->drupalGet("islandora/object/$pid"); - $this->assertResponse(404, "Object $pid successfully deleted."); - } - else { - $this->fail("Cannot delete object {$pid}; it is owned by non-test user {$object->owner}, and this function was called with the safety on."); - return FALSE; - } - } - - /** - * Constructs and ingests a Fedora object and datastream(s) via tuque. - * - * All keys inside the parameter arrays for this function are optional. it - * can be run simply by calling $this->ingestConstructedObject();. - * - * @param array $properties - * An array containing object information using these keys: - * 'label' - The object label; randomized if not set. - * 'pid' - 'namespace:pid', or just 'namespace' to generate the suffix. - * 'models' - An array that can contain multiple content model PIDs, or a - * string containing a single content model PID. - * 'owner' - The object's owner. Defaults to the currently logged-in user, - * if available. It is recommended to set this to a value that can be found - * in $this->users; otherwise, this object will have to be manually deleted. - * 'parent' - The PID of the parent collection. - * @param array $datastreams - * An array containing zero or more datastream arrays that use the keys: - * 'dsid' - the datastream ID; randomized if not set. - * 'path' - The path to the file to use; defaults to fixtures/test.jpg. - * 'control_group' - The single-letter control group identifier. - * 'mimetype' - The datastream's mimetype. - * - * @return bool|array - * FALSE if the object ingest failed, or the object array if successful. - */ - public function ingestConstructedObject(array $properties = array(), array $datastreams = array()) { - $tuque = islandora_get_tuque_connection($this->admin); - $repository = $tuque->repository; - if (!isset($properties['pid'])) { - $properties['pid'] = "islandora"; - } - $object = $repository->constructObject($properties['pid']); - - // Set the object properties before ingesting it. - if (isset($properties['label'])) { - $object->label = $properties['label']; - } - else { - $properties['label'] = $this->randomName(16); - $object->label = $properties['label']; - } - - if (isset($properties['owner'])) { - $object->owner = $properties['owner']; - } - elseif ($this->loggedInUser !== FALSE) { - $object->owner = $this->loggedInUser->name; - } - - if (isset($properties['models'])) { - try { - $object->models = (array) $properties['models']; - } - catch (Exception $e) { - $this->fail("Encountered an exception when trying to add content models to {$object->id}: $e"); - return FALSE; - } - } - - $repository->ingestObject($object); - if (!$object) { - $this->fail(t("Failed to ingest object."), 'Islandora'); - return FALSE; - } - else { - $this->pass(t("Ingested object %object", array('%object' => $object->id)), 'Islandora'); - } - - // Chuck in some datastreams. - if (!empty($datastreams)) { - foreach ($datastreams as $datastream) { - if (!isset($datastream['dsid'])) { - $datastream['dsid'] = $this->randomName(8); - } - if (!isset($datastream['path'])) { - $datastream['path'] = drupal_get_path('module', 'islandora') . '/tests/fixtures/test.jpg'; - } - if (!isset($datastream['control_group'])) { - $new_datastream = $object->constructDatastream($datastream['dsid']); - } - else { - $new_datastream = $object->constructDatastream($datastream['dsid'], $datastream['control_group']); - } - $new_datastream->label = $datastream['dsid']; - if (isset($datastream['mimetype'])) { - $new_datastream->mimetype = $datastream['mimetype']; - } - $new_datastream->setContentFromFile($datastream['path']); - $object->ingestDatastream($new_datastream); - } - } - - // Add a parent relationship, if necessary. - if (isset($properties['parent'])) { - $object->relationships->add(FEDORA_RELS_EXT_URI, 'isMemberOfCollection', $properties['parent']); - } - - return $object; - } - - /** - * Deletes all objects created by the given user. - * - * To safeguard against leaving test objects in the repository, this is called - * each time DrupalTestCase::run() calls tearDown(). This feature can be - * toggled by setting $this->deleteObjectsOnTeardown to TRUE or FALSE. - * - * @param object $username - * The user whose objects we'd like to remove. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function deleteUserCreatedObjects($username) { - if ($username === $this->configuration['admin_user']) { - $this->fail("This function will under no circumstance attempt deletion of all objects owned by the configured Fedora admin user ({$this->configuration['admin_user']}), as this could irreparably damage the repository.", 'Islandora'); - return FALSE; - } - - $query = << WHERE -{ - ?object "$username" -} -QUERY; - - $objects = $this->admin->repository->ri->sparqlQuery($query); - foreach ($objects as $object) { - $loaded_object = islandora_object_load($object['object']['value']); - islandora_delete_object($loaded_object); - if ($this->assertFalse(islandora_object_load($object['object']['value']), "Object {$object['object']['value']} successfully removed from repository.", 'Islandora')) { - return FALSE; - } - return TRUE; - } - } - - /** - * These are a few quick helper functions to fill in a gap in the standard - * DrupalWebTestCase where no function exists to test for the simple existence - * or non-existence of an error. - */ - - /** - * Asserts that an error is found in $this->content. - * - * @param string $message - * The message to pass on to the results. - * @param string $group - * The group to place the result in. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function assertError($message = '', $group = 'Other') { - if (!$message) { - $message = "Error found on current page when error was expected."; - } - return $this->assertRaw("
", $message, $group); - } - - /** - * Asserts that no error is found in $this->content. - * - * @param string $message - * The message to pass on to the results. - * @param string $group - * The group to place the result in. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function assertNoError($message = '', $group = 'Other') { - if (!$message) { - $message = "No error found on current page when no error was expected."; - } - return $this->assertNoRaw("
", $message, $group); - } - - /** - * Asserts that a warning is found in $this->content. - * - * @param string $message - * The message to pass on to the results. - * @param string $group - * The group to place the result in. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function assertWarning($message = '', $group = 'Other') { - if (!$message) { - $message = "Warning found on current page when warning was expected."; - } - return $this->assertRaw("
", $message, $group); - } - - /** - * Asserts that no warning is found in $this->content. - * - * @param string $message - * The message to pass on to the results. - * @param string $group - * The group to place the result in. - * - * @return bool - * TRUE on success, FALSE on failure. - */ - public function assertNoWarning($message = '', $group = 'Other') { - if (!$message) { - $message = "No warning found on current page when no warning was expected."; - } - return $this->assertNoRaw("
", $message, $group); - } - - /** - * Makes a drupalPost() request, using the form submit button's ID. - * - * Because drupalPost() is silly and doesn't let us choose which button we'd - * like to select if multiple buttons have the same value, this function - * allows us to select whatever button we'd like based on the ID instead. - * - * This is done via the absolutely hilarious method of fudging the actual - * button labels that don't have the ID we want, so that the only one left - * with the correct label is the one with the right ID. - * - * @see DrupalWebTestCase::drupalPost() - * - * @param string $path - * Location of the post form. - * @param array $edit - * Field data in an associative array. - * @param string $submit - * Value of the submit button whose click is to be emulated. - * @param string $id - * ID of the submit button whose click is to be emulated. - * @param array $options - * Options to be forwarded to url(). - * @param array $headers - * An array containing additional HTTP request headers, each formatted as - * "name: value". - * @param null $form_html_id - * (optional) HTML ID of the form to be submitted. - * @param null $extra_post - * (optional) A string of additional data to append to the POST submission. - * - * @return bool|string - * The content from the POST request's curlExec, or FALSE on fail. - */ - public function drupalPostByID($path, $edit, $submit, $id, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) { - $buttons = $this->xpath("//input[@type=\"submit\" and @value=\"{$submit}\"]"); - if (empty($buttons)) { - $this->fail("No buttons found on the page with value '$submit'"); - return FALSE; - } - $i = 0; - foreach ($buttons as $button) { - if ($button['id'] !== $id) { - $button['value'] .= "_$i"; - $i++; - } - } - return $this->drupalPost($path, $edit, $submit, $options, $headers, $form_html_id, $extra_post); - } - -}