<?php
/**
* Test methods for the P4 Job class.
*
* @copyright 2012 Perforce Software. All rights reserved.
* @license Please see LICENSE.txt in top-level folder of this distribution.
* @version <release>/<patch>
*/
namespace P4Test\Spec;
use P4Test\TestCase;
use P4\Spec\Job;
use P4\Spec\User;
use P4\Log\Logger;
use P4\Spec\Client;
use P4\Spec\Exception\NotFoundException;
use Zend\Log\Logger as ZendLogger;
use Zend\Log\Writer\Mock as MockLog;
class JobTest extends TestCase
{
/**
* Test fetching a job.
*/
public function testFetch()
{
// ensure fetch fails for a non-existant job.
$jobId = 'alskdfj2134';
try {
Job::fetch($jobId);
$this->fail('Fetch should fail for a non-existant job.');
} catch (NotFoundException $e) {
$this->assertSame(
"Cannot fetch job $jobId. Record does not exist.",
$e->getMessage(),
'Expected error fetching a non-existant job.'
);
} catch (\Exception $e) {
$this->fail('Unexpected exception fetching a non-existant job.');
}
// ensure fetch fails with an empty id
$jobId = '';
try {
Job::fetch($jobId);
$this->fail('Unexpected success fetching an empty job id.');
} catch (\InvalidArgumentException $e) {
$this->assertSame(
'Must supply a valid id to fetch.',
$e->getMessage(),
'Expected error fetching an empty job id.'
);
} catch (\Exception $e) {
$this->fail('Unexpected exception fetching an empty job id.');
}
}
/**
* Verify one command populates a job.
*/
public function testFetchOneCommand()
{
$job = new Job;
$description = "test job for fetching\n";
$job->set('Description', $description)->save();
try {
$job = Job::fetch('job000001');
$this->assertSame($description, $job->getDescription());
} catch (\Exception $e) {
$this->fail("Unexpected exception fetching a job.");
}
// ensure it only takes one command to fetch a job and read
// basic values from it -- we verify this by peeking at the log
$original = Logger::hasLogger() ? Logger::getLogger() : null;
$logger = new ZendLogger;
$mock = new MockLog;
$logger->addWriter($mock);
Logger::setLogger($logger);
$fetched = Job::fetch('job000001');
$fetched->get();
$this->assertSame(1, count($mock->events));
// restore original logger if there is one.
Logger::setLogger($original);
}
/**
* Test exists.
*/
public function testExists()
{
// ensure id-exists returns false for non-existant job
$this->assertFalse(Job::exists('alsdjf'), 'Given job id should not exist.');
// ensure id-exists returns false for invalid job
$this->assertFalse(Job::exists('-job1'), 'Invalid job id should not exist.');
// create job and ensure it exists.
$job = new Job;
$job->set('Description', 'test')->save();
$this->assertTrue(Job::exists('job000001'), 'Given job id should exist.');
}
/**
* Test saving a job.
*/
public function testSave()
{
$job = new Job;
$description = 'test!';
$job->set('Description', $description);
// demonstrate that pre-save description is unmodified.
$this->assertSame(
$description,
$job->get('Description'),
'Expected pre-fetch description'
);
$job->save();
$firstId = 'job000001';
$this->assertSame($firstId, $job->getId(), 'Expected id');
$job = Job::fetch($firstId);
$this->assertSame($firstId, $job->getId(), 'Expected id');
// demonstrate that post-save description has had whitespace
// management performed by the server.
$this->assertSame(
"$description\n",
$job->get('Description'),
'Expected post-fetch description'
);
}
/**
* Test deleting a job.
*/
public function testDelete()
{
// make a few jobs
$expectedIds = array();
$expectedDescriptions = array();
for ($i = 0; $i < 5; $i++) {
$job = new Job;
$description = "job $i\n";
$job->set('Description', $description);
$job->save();
$expectedIds[] = $job->getId();
$expectedDescriptions[] = $description;
}
$jobs = Job::fetchAll();
$this->assertTrue($jobs->count() == 5, 'Expected job count');
$jobIds = array();
$descriptions = array();
foreach ($jobs as $job) {
$jobIds[] = $job->getId();
$descriptions[] = $job->get('Description');
}
$this->assertSame(
$expectedIds,
$jobIds,
'Expected job ids'
);
$this->assertSame(
$expectedDescriptions,
$descriptions,
'Expected job descriptions'
);
$theId = $jobs->nth(2)->getId();
$this->assertTrue(
Job::exists($theId),
'Given job id should exist.'
);
// now delete a job
$job = $jobs->nth(2);
$job->delete();
// adjust expectations
array_splice($expectedIds, 2, 1);
array_splice($expectedDescriptions, 2, 1);
// refetch and test that the deleted job no longer exists
// and that the non-deleted jobs still exist
$jobs = Job::fetchAll();
$this->assertTrue($jobs->count() == 4, 'Expected job count');
$jobIds = array();
$descriptions = array();
foreach ($jobs as $job) {
$jobIds[] = $job->getId();
$descriptions[] = $job->get('Description');
}
$this->assertSame(
$expectedIds,
$jobIds,
'Expected job ids'
);
$this->assertSame(
$expectedDescriptions,
$descriptions,
'Expected job descriptions'
);
$this->assertFalse(
Job::exists($theId),
'Given job id should not exist.'
);
}
/**
* Test fetchAll.
*/
public function testFetchAll()
{
$expectedJobIds = array();
$expectedDescriptions = array();
for ($i = 0; $i < 10; $i++) {
$job = new Job;
$description = "test job $i";
$job->set('Description', $description);
$job->save();
$expectedDescriptions[] = "$description\n";
$expectedJobIds[] = $job->getId();
}
$jobs = Job::fetchAll();
$this->assertTrue($jobs->count() == 10, 'Expected job count');
$jobIds = array();
$descriptions = array();
foreach ($jobs as $job) {
$jobIds[] = $job->getId();
$descriptions[] = $job->get('Description');
}
$this->assertSame(
$expectedJobIds,
$jobIds,
'Expected job ids'
);
$this->assertSame(
$expectedDescriptions,
$descriptions,
'Expected job descriptions'
);
}
/**
* get/set value are tested already for code indexed mutators. Test them for
* fields without mutator/accessors.
*/
public function testGetSet()
{
// add the field 'NewField' to do our testing on.
$fields = \P4\Spec\Definition::fetch('job')->getFields();
$fields['NewField'] = array (
'code' => '110',
'dataType' => 'word',
'displayLength' => '32',
'fieldType' => 'required',
);
\P4\Spec\Definition::fetch('job')->setFields($fields)->save();
// test in memory object
$job = new Job;
$job->setDescription('test');
$this->assertSame(
null,
$job->get('NewField'),
'Expected matching starting value'
);
$job->set('NewField', 'test');
$this->assertSame(
'test',
$job->get('NewField'),
'Expected matching value after set'
);
// save it and refetch to verify it is still good
$job->save();
$job = Job::fetch($job->getId());
$this->assertSame(
'test',
$job->get('NewField'),
'Expected matching value after save/fetch'
);
}
/**
* Test setting invalid inputs for Status/User/Description
*/
public function testBadSetStatusUserDescription()
{
$methods = array(
'setStatus' => 'Status must be a string or null',
'setUser' => 'User must be a string, P4\Spec\User or null',
'setDescription' => 'Description must be a string or null',
);
$tests = array(
array(
'title' => __LINE__.' int',
'value' => 10
),
array(
'title' => __LINE__.' bool',
'value' => true
),
array(
'title' => __LINE__.' float',
'value' => 10.0
),
array(
'title' => __LINE__.' P4\Spec\Client',
'value' => Client::fetchAll(array(Client::FETCH_MAXIMUM => 1))
),
);
foreach ($methods as $method => $expectedError) {
foreach ($tests as $test) {
try {
$job = new Job;
$job->{$method}($test['value']);
$this->fail(
$method.' '.$test['title'].': Unexpected success'
);
} catch (\PHPUnit\Framework\AssertionFailedError $e) {
$this->fail($e->getMessage());
} catch (\InvalidArgumentException $e) {
$this->assertSame(
$expectedError,
$e->getMessage(),
$method.' '.$test['title'].': unexpected exception message'
);
} catch (\Exception $e) {
$this->fail(
$method.' '.$test['title'].
': unexpected exception ('. get_class($e) .') '.
$e->getMessage()
);
}
}
}
}
/**
* Test setting valid inputs for Status
*/
public function testGoodSetStatus()
{
$tests = array(
array(
'title' => __LINE__.' empty string',
'value' => ''
),
array(
'title' => __LINE__.' null',
'value' => null
),
array(
'title' => __LINE__.' valid string',
'value' => 'closed',
'canSave' => true,
),
array(
'title' => __LINE__.' valid string',
'value' => 'suspended',
'canSave' => true,
),
array(
'title' => __LINE__.' valid string',
'value' => 'open',
'canSave' => true,
),
);
foreach ($tests as $test) {
$title = $test['title'];
$value = $test['value'];
$out = array_key_exists('out', $test) ? $test['out'] : $test['value'];
$job = new Job;
// in memory test via setStatus
$job->setStatus($value);
$this->assertSame(
$out,
$job->getStatus(),
$title.': expected to match set value'
);
$this->assertSame(
$out,
$job->get('Status'),
$title.': Expected get() to match'
);
// via set comparison
$job = new Job;
$job->set('Status', $value);
$this->assertSame(
$out,
$job->get('Status'),
$title.': Expected getStatus to match get'
);
$this->assertSame(
$out,
$job->getStatus(),
$title.': Expected set to match getStatus'
);
if (array_key_exists('canSave', $test)) {
// post save test
$job->setDescription('blah')->setUser('user1')->save();
$this->assertSame(
$out,
$job->getStatus(),
'Expected getStatus to match post save'
);
$this->assertSame(
$out,
$job->get('Status'),
'Expected get(Status) to match post save'
);
// post fetch test
$job = Job::fetch($job->getId());
$this->assertSame(
$out,
$job->getStatus(),
'Expected getStatus to match post fetch'
);
$this->assertSame(
$out,
$job->get('Status'),
'Expected get(Status) to match post fetch'
);
}
}
}
/**
* Test setting valid inputs for User
*/
public function testGoodSetUser()
{
$user = new User;
$user->setId('bob');
$tests = array(
array(
'title' => __LINE__.' empty string',
'value' => ''
),
array(
'title' => __LINE__.' null',
'value' => null
),
array(
'title' => __LINE__.' valid string',
'value' => 'user1',
'canSave' => true,
),
array(
'title' => __LINE__.' valid object',
'value' => $user,
'out' => $user->getId(),
'canSave' => true,
),
);
foreach ($tests as $test) {
$title = $test['title'];
$value = $test['value'];
$out = array_key_exists('out', $test) ? $test['out'] : $test['value'];
$job = new Job;
// in memory test via setField
$job->setUser($value);
$this->assertSame(
$out,
$job->getUser(),
$title.': expected to match set value'
);
$this->assertSame(
$out,
$job->get('User'),
$title.': Expected get() to match'
);
// via set comparison
$job = new Job;
$job->set('User', $value);
$this->assertSame(
$out,
$job->get('User'),
$title.': Expected to match get'
);
$this->assertSame(
$out,
$job->getUser(),
$title.': Expected set to match getUser'
);
if (array_key_exists('canSave', $test)) {
// post save test
$job->setDescription('blah')->setStatus('open')->save();
$this->assertSame(
$out,
$job->getUser(),
'Expected getUser to match post save'
);
$this->assertSame(
$out,
$job->get('User'),
'Expected get(User) to match post save'
);
// post fetch test
$job = Job::fetch($job->getId());
$this->assertSame(
$out,
$job->getUser(),
'Expected getUser to match post fetch'
);
$this->assertSame(
$out,
$job->get('User'),
'Expected get(User) to match post fetch'
);
}
}
}
/**
* Test setting valid inputs for Description
*/
public function testGoodSetDescription()
{
$tests = array(
array(
'title' => __LINE__.' empty string',
'value' => ''
),
array(
'title' => __LINE__.' null',
'value' => null
),
array(
'title' => __LINE__.' valid string',
'value' => "test description\n",
'canSave' => true,
),
array(
'title' => __LINE__.' valid multi-line string',
'value' => "test of\nmultiline\ndescriptoin!\n",
'canSave' => true,
),
);
foreach ($tests as $test) {
$title = $test['title'];
$value = $test['value'];
$out = array_key_exists('out', $test) ? $test['out'] : $test['value'];
$job = new Job;
// in memory test via setField
$job->setDescription($value);
$this->assertSame(
$out,
$job->getDescription(),
$title.': expected to match set value'
);
$this->assertSame(
$out,
$job->get('Description'),
$title.': Expected get() to match'
);
// via set comparison
$job = new Job;
$job->set('Description', $value);
$this->assertSame(
$out,
$job->get('Description'),
$title.': Expected to match get'
);
$this->assertSame(
$out,
$job->getDescription(),
$title.': Expected set to match getDescription'
);
if (array_key_exists('canSave', $test)) {
// post save test
$job->setUser('user1')->setStatus('open')->save();
$this->assertSame(
$out,
$job->getDescription(),
'Expected getDescription to match post save'
);
$this->assertSame(
$out,
$job->get('Description'),
'Expected get(Description) to match post save'
);
// post fetch test
$job = Job::fetch($job->getId());
$this->assertSame(
$out,
$job->getDescription(),
'Expected getDescription to match post fetch'
);
$this->assertSame(
$out,
$job->get('Description'),
'Expected get(Description) to match post fetch'
);
}
}
}
/**
* test the get date function
*/
public function testGetDate()
{
$job = new Job;
$this->assertSame(
null,
$job->getDate(),
'Expected starting date to match'
);
$job->setUser('user1')->setStatus('open')->setDescription('blah')->save();
// convert to unix time (accounting for timezone) and verify it looks ok
$dateTime = \DateTime::createFromFormat('Y/m/d H:i:s', $job->getDate(), $this->p4->getTimeZone());
$this->assertLessThan(
2,
abs((int) $dateTime->format('U') - time()),
'Expected converted data/time to be within range post save'
);
// also confirm the built-in conversion on the job object spits out a good unixtime
$this->assertSame(
(int) $dateTime->format('U'),
$job->getTime(),
'Expected time to be within range post save'
);
}
/**
* Tests invalid options and option combos
*/
public function testFetchAllBadOptions()
{
$tests = array(
array(
'title' => __LINE__.' integer filter',
'options' => array(Job::FETCH_BY_FILTER => 0),
'exception' => 'Fetch by Filter expects a non-empty string as input'
),
array(
'title' => __LINE__.' empty string filter',
'options' => array(Job::FETCH_BY_FILTER => ""),
'exception' => 'Fetch by Filter expects a non-empty string as input'
),
array(
'title' => __LINE__.' empty string filter w/whitespace',
'options' => array(Job::FETCH_BY_FILTER => " "),
'exception' => 'Fetch by Filter expects a non-empty string as input'
),
array(
'title' => __LINE__.' integer filter',
'options' => array(Job::FETCH_BY_FILTER => 10),
'exception' => 'Fetch by Filter expects a non-empty string as input'
),
);
foreach ($tests as $test) {
try {
Job::fetchAll($test['options']);
$this->fail($test['title'].': unexpected success');
} catch (\PHPUnit\Framework\AssertionFailedError $e) {
$this->fail($e->getMessage());
} catch (\InvalidArgumentException $e) {
$this->assertSame(
$test['exception'],
$e->getMessage(),
$test['title'].': unexpected exception message'
);
} catch (\Exception $e) {
$this->fail(
$test['title'].
': unexpected exception ('. get_class($e) .') '.
$e->getMessage()
);
}
}
}
/**
* Test fetchAll with FETCH_DESCRIPTION = false and = true
*/
public function testFetchAllDescriptions()
{
for ($i=0; $i<4; $i++) {
$job = new Job;
$job->setId((string)$i)->setUser('user'.$i)->setStatus('open')
->setDescription('test: '.$i."\n")->save();
}
// eval a mock object into existence which adds a 'getRawValues' function
$mockCode = 'class P4_JobMock extends \\P4\\Spec\\Job {
public function getRawValues()
{
return $this->values;
}
}';
if (!class_exists('P4_JobMock')) {
eval($mockCode);
}
// Test with FETCH_DESCRIPTION off
$jobs = \P4_JobMock::fetchAll(array(Job::FETCH_DESCRIPTION => false));
foreach ($jobs as $job) {
$this->assertFalse(
array_key_exists('Description', $job->getRawValues()),
'Job: '.$job->getId().' expected Description to be non-existent'
);
$this->assertSame(
"test: ".$job->getId()."\n",
$job->getDescription(),
'Job: '.$job->getId().' expected Description to autoload'
);
}
// Test with FETCH_DESCRIPTION on
$jobs = \P4_JobMock::fetchAll(array(Job::FETCH_DESCRIPTION => true));
foreach ($jobs as $job) {
$this->assertTrue(
array_key_exists('Description', $job->getRawValues()),
'Job: '.$job->getId().' expected Description to exist'
);
$values = $job->getRawValues();
$this->assertSame(
"test: ".$job->getId()."\n",
$values['Description'],
'Job: '.$job->getId().' expected Description to match'
);
$this->assertSame(
"test: ".$job->getId()."\n",
$job->getDescription(),
'Job: '.$job->getId().' expected Description to match via accessor'
);
}
// Test default is FETCH_DESCRIPTION on
$explicitJobs = \P4_JobMock::fetchAll(array(Job::FETCH_DESCRIPTION => true));
$defaultJobs = \P4_JobMock::fetchAll();
foreach ($explicitJobs as $key => $job) {
$this->assertSame(
$job->getRawValues(),
$defaultJobs[$key]->getRawValues(),
'Expeted default to be fetch description = true'
);
}
}
/**
* Verify a variety of unusual ids don't cause trouble
*/
public function testCrazyIds()
{
$ids = array(
'!bang', '$test', '%percent', '&and', '\'', '(open', ')closed', ',comma', '.dot',
'042709', '30387b', '3spaces', '<ab>', '<abc>', '<open', '>new', '?question', '[open',
']closed', '^carrot', '^new', '_Myjob001', '__job_w/_leading_and_trailing_white_space_',
'_underscore', 'www.hello'
);
foreach ($ids as $id) {
$job = new Job;
$job->setId($id)
->setDescription($id)
->save();
}
}
/**
* Verify ID's with - don't cause erroneous fetch.
*/
public function testDashFetches()
{
$ids = array('foo', 'foo2', 'foo-bar', 'foo-biz', 'bar-foo', 'biz-foo');
foreach ($ids as $id) {
$job = new Job;
$job->setId($id)
->setDescription($id)
->save();
}
$this->assertSame(
array('foo'),
Job::fetchAll(array(Job::FETCH_BY_IDS => 'foo'))->invoke('getId'),
'expected fetch by ids to work'
);
}
public function testCreatedModifiedFieldMethods()
{
$job = new Job;
// should have mod-date field by default
$this->assertTrue($job->hasModifiedDateField());
$this->assertSame('Date', $job->getModifiedDateField());
// should NOT have create-date field
$this->assertFalse($job->hasCreatedDateField());
try {
$job->getCreatedDateField();
$this->fail();
} catch (\P4\Spec\Exception\Exception $e) {
$this->assertTrue(true);
}
// add a created date field
$spec = $job->getSpecDefinition();
$fields = $spec->getFields();
$fields['CreatedDate'] = array(
'code' => 106,
'dataType' => 'date',
'displayLength' => 20,
'fieldType' => 'once',
'default' => '$now'
);
$spec->setFields($fields)->save();
// should now have create-date field
$this->assertTrue($job->hasCreatedDateField());
$this->assertSame('CreatedDate', $job->getCreatedDateField());
// should have created by field.
$this->assertTrue($job->hasCreatedByField());
$this->assertSame('User', $job->getCreatedByField());
// should NOT have modified by field.
$this->assertFalse($job->hasModifiedByField());
try {
$job->getModifiedByField();
$this->fail();
} catch (\P4\Spec\Exception\Exception $e) {
$this->assertTrue(true);
}
// add a modified by field
$spec = $job->getSpecDefinition();
$fields = $spec->getFields();
$fields['ModifiedBy'] = array(
'code' => 107,
'dataType' => 'word',
'displayLength' => 20,
'fieldType' => 'always',
'default' => '$user'
);
$spec->setFields($fields)->save();
// should have mod-date field by default
$this->assertTrue($job->hasModifiedByField());
$this->assertSame('ModifiedBy', $job->getModifiedByField());
}
}