ContentIntegrationTest.php #1

  • //
  • guest/
  • perforce_software/
  • chronicle/
  • main/
  • application/
  • workflow/
  • tests/
  • ContentIntegrationTest.php
  • View
  • Commits
  • Open Download .zip Download (42 KB)
<?php
/**
 * Test that workflow is integrated with content as expected.
 *
 * @copyright   2011 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */
class Workflow_Test_ContentIntegrationTest extends ModuleControllerTest
{
    /**
     * All tests here need content types, workflows and 'editor' access.
     */
    public function setUp()
    {
        parent::setUp();

        // install default content types and workflows.
        P4Cms_Content_Type::installDefaultTypes();
        Workflow_Model_Workflow::installDefaultWorkflows();
    }

    /**
     * Ensure workflow sub-form is present when adding content
     * that has associated workflow
     */
    public function testContentSubForm()
    {
        $this->utility->impersonate('editor');

        $this->dispatch('/content/add/type/basic-page');

        // ensure workflow sub-form is here.
        $this->assertQuery('.workflow-sub-form');

        // basic page uses 'simple workflow', default state is 'draft'.
        $this->assertQuery('#workflow-state-draft[checked="checked"]');

        // ensure transitions are present also.
        $workflow = Workflow_Model_Workflow::fetch('simple');
        $state    = $workflow->getStateModel('draft');
        foreach ($state->getTransitions() as $transition => $details) {
            $this->assertQuery('#workflow-state-' . $transition);
        }
    }

    /**
     * Ensure that if there are elements in the sub-forms with names matching
     * entry fields, then they are not populated with those values.
     */
    public function testSubFormElementsCollision()
    {
        $this->utility->impersonate('editor');

        // save entry with a comment field that collides with
        // comment input element in the workflow sub-form
        $entry = P4Cms_Content::store(
            array(
                'id'            => 'test',
                'contentType'   => 'basic-page',
                'title'         => 'test-title',
                'workflowState' => 'published',
                'comment'       => 'comment_test'
            )
        );

        // ensure workflow comment field is not populated with comment_test value
        // when editing a content that has associated workflow
        $this->dispatch('/content/edit/id/' . $entry->getId());

        $this->assertNotQueryContentContains(
            "textarea#workflow-comment",
            'comment_test',
            "Expected workflow comment doesn't contain entry's comment value."
        );
    }

    /**
     * Ensure saving content, saves state.
     */
    public function testContentSave()
    {
        $this->utility->impersonate('editor');

        $this->request->setMethod('post')
                      ->setPost('format',      'json')
                      ->setPost('contentType', 'basic-page')
                      ->setPost('title',       'test-title')
                      ->setPost('body',        'test-body')
                      ->setPost(
                        'workflow',
                        array(
                            'state'     => 'review',
                            'scheduled' => 'false',
                            'comment'   => 'test'
                        )
                      );

        $this->dispatch('/content/add/');

        $data    = Zend_Json::decode($this->getResponse()->getBody());
        $content = P4Cms_Content::fetch($data['contentId']);
        $change  = $content->toP4File()->getChange();
        $this->assertSame('review', $content->getValue('workflowState'));
    }

    /**
     * Ensure scheduled data are saved when saving entry with a scheduled transition.
     */
    public function testContentSaveScheduled()
    {
        $this->utility->impersonate('editor');

        // set a timestamp that is in the future where the day, month and year are not the
        // same as today (i.e. it must be at least one day from now); we set today + 2 days
        $timestamp = time() + 172800;

        $this->request->setMethod('post')
                      ->setPost('format',      'json')
                      ->setPost('contentType', 'basic-page')
                      ->setPost('title',       'test-title')
                      ->setPost('body',        'test-body')
                      ->setPost(
                        'workflow',
                        array(
                            'state'           => 'review',
                            'scheduled'       => 'true',
                            'scheduledDate'   => date('Y-m-d', $timestamp),
                            'scheduledTime'   => '07:00'
                        )
                      );

        $this->dispatch('/content/add/');

        $data    = Zend_Json::decode($this->getResponse()->getBody());
        $content = P4Cms_Content::fetch($data['contentId']);
        $this->assertSame('draft',  $content->getValue('workflowState'));
        $this->assertSame('review', $content->getValue('workflowScheduledState'));
        $this->assertSame(
            strtotime(date('Y-m-d 07:00', $timestamp)),
            (int) $content->getValue('workflowScheduledTime')
        );
    }

    /**
     * Ensure editing shows current state.
     */
    public function testContentEdit()
    {
        $this->utility->impersonate('editor');

        // ensure current state is checked if entry has no scheduled transition
        $entry = $this->_makeContentEntry('published');
        $this->dispatch('/content/edit/id/' . $entry->getId());
        $this->assertQuery('#workflow-state-published[checked="checked"]');
    }

    /**
     * Ensure edition shows scheduled state if entry has scheduled transition.
     */
    public function testScheduledContentEdit()
    {
        $this->utility->impersonate('editor');

        // if entry has scheduled transition, ensure that target state of scheduled transition is
        // checked
        $entry = $this->_makeContentEntry('review', '', 'draft', '1');
        $this->dispatch('/content/edit/id/' . $entry->getId());
        $this->assertQuery('#workflow-state-draft[checked="checked"]');
    }

    /**
     * Test content access for anonymous role.
     */
    public function testAccessAnonymous()
    {
        $published   = $this->_makeContentEntry('published');
        $unPublished = $this->_makeContentEntry();

        // ensure anonymous users can see published content
        // but cannot see un-published content
        $this->utility->impersonate('anonymous');
        $this->assertTrue(P4Cms_Content::exists($published->getId()));
        $this->assertFalse(P4Cms_Content::exists($unPublished->getId()));
    }

    /**
     * Test content access for editor role.
     */
    public function testAccessEditor()
    {
        $unPublished = $this->_makeContentEntry();

        // ensure un-published content is accessible to editors
        $this->utility->impersonate('editor');
        $acl = P4Cms_Site::fetchActive()->getAcl();
        $this->assertTrue(P4Cms_Content::exists($unPublished->getId()));
    }

    /**
     * Test content access for member role.
     */
    public function testAccessMember()
    {
        $published   = $this->_makeContentEntry('published', 'foo');
        $unPublished = $this->_makeContentEntry();

        $this->utility->impersonate('member');
        $userId = P4Cms_User::fetchActive()->getId();

        // ensure un-published content is accessible by members only if they own it
        $this->assertFalse(P4Cms_Content::exists($unPublished->getId()));
        $unPublished->setOwner($userId)->save();
        $this->assertTrue(P4Cms_Content::exists($unPublished->getId()));

        // ensure that member cannot see 'publish' option if not having 'publish' permission
        $acl = P4Cms_Site::fetchActive()->getAcl();
        $acl->allow('member', 'content', array('add', 'edit', 'edit-all'));

        $this->dispatch('/add/type/basic-page');
        $this->_verifyWorkflowStatesRadio(array('draft', 'review'), 'draft');

        // add publish permission and verify that user can see publish option
        $acl->allow('member', 'content', array('publish'));
        $this->resetRequest()->resetResponse();
        $this->dispatch('/add/type/basic-page');
        $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'draft');

        // ensure that member, although lacking 'access-unpublished' permission, can
        // unpublish content if is an owner
        $this->resetRequest()->resetResponse();
        $this->dispatch('/content/index/edit/id/' . $published->getId());

        // not an owner - should see only published state
        $this->_verifyWorkflowStatesRadio(array('published'), 'published');

        // add 'access-unpublished' and verify again
        $acl->allow('member', 'content', array('access-unpublished'));
        $this->resetRequest()->resetResponse();
        $this->dispatch('/content/index/edit/id/' . $published->getId());
        $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'published');

        // remove access-published permission, but make user owner and verify again
        $acl->deny('member', 'content', array('access-unpublished'));
        $published->setOwner($userId)->save();
        $this->resetRequest()->resetResponse();
        $this->dispatch('/content/index/edit/id/' . $published->getId());
        $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'published');
    }

    /**
     * Verify transition conditions. Dispatch to content add/edit action to simulate
     * creating/updating content entry when certain transition conditions apply.
     */
    public function testWorkflowConditions()
    {
        // create workflow we will test against -
        // modify simple workflow to remove published to draft transition
        $workflow       = Workflow_Model_Workflow::fetch('simple');
        $workflowStates = $workflow->getStates();
        unset($workflowStates['published']['transitions']['draft']);
        $workflow->setStates($workflowStates)
                 ->save();

        // set necessary permissions
        $this->utility->impersonate('editor');

        // verify all transitions are available when adding a new content
        $this->dispatch('/add/type/basic-page');
        $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'draft');

        // disable promoting to review
        $states = $workflowStates;
        $states['draft']['transitions']['review']['conditions'] = array('false');
        $workflow->setStates($states)
                 ->save();

        $this->resetRequest()
             ->resetResponse();
        $this->dispatch('/add/type/basic-page');
        $this->_verifyWorkflowStatesRadio(array('draft', 'published'), 'draft');

        // test with edit action
        $entry = $this->_makeContentEntry('review');
        $workflow->setStates($workflowStates)
                 ->save();
        $this->resetRequest()
             ->resetResponse();
        $this->dispatch('/content/index/edit/id/' . $entry->getId());
        $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'review');

        // add Contains condition
        $states = $workflowStates;
        $states['review']['transitions']['published']['conditions'] = array(
            'contains' => array(
                'string'    => 'find me'
            )
        );
        $workflow->setStates($states)
                 ->save();

        $this->resetRequest()
             ->resetResponse();
        $this->dispatch('/content/index/edit/id/' . $entry->getId());
        $this->_verifyWorkflowStatesRadio(array('draft', 'review'), 'review');

        // modify entry to satisfy the condition
        $entry = P4Cms_Content::fetch($entry->getId());
        $entry->setValue('body', 'Now body contains find me string.')
              ->save();

        $this->resetRequest()
             ->resetResponse();
        $this->dispatch('/content/index/edit/id/' . $entry->getId());
        $this->_verifyWorkflowStatesRadio(array('draft', 'review', 'published'), 'review');

        // try to modify content via post to ensure transition conditions must be met
        // also ensure conditions are evaluated agains posted data
        $this->resetRequest()
             ->resetResponse();

        $params = array(
            'contentType'   => 'basic-page',
            'title'         => 'test-title',
            'body'          => 'new body',
            'workflow'      => array(
                'state'     => 'published',
                'scheduled' => 'false'
            )
        );
        $this->request->setParam('id', $entry->getId());
        $this->request->setMethod('POST');
        $this->request->setPost($params);
        $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId());

        // verify entry has not been modified
        $entry = P4Cms_Content::fetch($entry->getId());
        $this->assertSame(
            'review',
            $entry->getValue('workflowState'),
            "Expected workflow state has not changed as transition is invalid."
        );

        // try again with no changes made so it should work
        $this->resetRequest()
             ->resetResponse();

        $params = array(
            'contentType'   => 'basic-page',
            'title'         => 'test-title',
            'body'          => 'changed body to contain find me string',
            'workflow'      => array(
                'state'     => 'published',
                'scheduled' => 'false'
            )
        );
        $this->request->setMethod('POST');
        $this->request->setPost($params);
        $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId());

        $responseBody = $this->response->getBody();
        $this->assertModule('content', 'Last module run should be content module.'. $responseBody);
        $this->assertController('index', 'Expected controller.'. $responseBody);
        $this->assertAction('edit', 'Expected action.'. $responseBody);

        // verify entry has been modified
        $entry = P4Cms_Content::fetch($entry->getId());
        $this->assertSame(
            'published',
            $entry->getValue('workflowState'),
            "Expected workflow state has been changed as transition is valid."
        );

        // check state options
        $this->resetRequest()
             ->resetResponse();
        $this->dispatch('/content/index/edit/id/' . $entry->getId());
        $this->_verifyWorkflowStatesRadio(array('review', 'published'), 'published');
    }

    /**
     * Verify transition actions. Dispatch to content edit action to simulate
     * updating content entry and ensure transition actions are invoked.
     */
    public function testWorkflowActions()
    {
        // test with simple workflow added by SendEmail action to a certain transitions
        $workflow       = Workflow_Model_Workflow::fetch('simple');
        $workflowStates = $workflow->getStates();

        // add sendEmail action to the review->published and review->draft transitions
        $workflowStates['review']['transitions']['published']['actions'] = array(
            'sendEmail' => array(
                'to'        => '[email protected]',
                'subject'   => 'content published'
            )
        );
        $workflowStates['review']['transitions']['draft']['actions'] = array(
            'sendEmail' => array(
                'to'        => '[email protected]',
                'subject'   => 'content demoted to draft'
            )
        );
        $workflow->setStates($workflowStates)
                 ->save();

        // set custom email transport to prevent from sending real emails and to allow
        // checking what emails would be sent
        $transport = new Workflow_Test_MailTransport;
        Zend_Mail::setDefaultTransport($transport);

        // set necessary permissions
        $this->utility->impersonate('editor');

        // create new entry
        $entry = $this->_makeContentEntry();

        // edit entry to promote to review state
        $params = array(
            'contentType'   => 'basic-page',
            'title'         => 'test-title',
            'workflow'      => array(
                'state'     => 'review',
                'scheduled' => 'false'
            )
        );
        $this->request->setMethod('POST');
        $this->request->setPost($params);
        $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId());

        // ensure entry has been modified
        $entry = P4Cms_Content::fetch($entry->getId());
        $this->assertSame(
            'review',
            $entry->getValue('workflowState'),
            "Expected workflow state has been changed to review."
        );

        // ensure email has not been sent
        $this->assertSame(
            0,
            count($transport->getSentMails()),
            "Expected no sendEmail action has occured."
        );

        // publish content
        $transport->reset();
        $this->resetRequest()
             ->resetResponse();

        $params = array(
            'contentType'   => 'basic-page',
            'title'         => 'test-title',
            'workflow'      => array(
                'state'     => 'published',
                'scheduled' => 'false'
            )
        );
        $this->request->setMethod('POST');
        $this->request->setPost($params);
        $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId());

        // ensure entry has been updated
        $entry = P4Cms_Content::fetch($entry->getId());
        $this->assertSame(
            'published',
            $entry->getValue('workflowState'),
            "Expected workflow state has been changed to published."
        );

        // ensure correct email has been sent
        $sentEmails = $transport->getSentMails();
        $this->assertSame(
            1,
            count($sentEmails),
            "Expected sendEmail action has occured."
        );

        // verify email recipient and subject
        $this->assertSame(
            '[email protected]',
            $sentEmails[0]['to'],
            "Expected email recipient."
        );
        $this->assertSame(
            'content published',
            $sentEmails[0]['subject'],
            "Expected email subject."
        );

        // ensure actions are not invoked if transition conditions are not met
        $workflowStates['published']['transitions']['review']['actions'] = array(
            'sendEmail'     => array(
                'to'        => '[email protected]',
                'subject'   => 'published -> review'
            )
        );
        $workflowStates['published']['transitions']['review']['conditions'] = array('false');
        $workflow->setStates($workflowStates)
                 ->save();

        // try to demote content to review
        $transport->reset();
        $this->resetRequest()
             ->resetResponse();

        $params = array(
            'contentType'   => 'basic-page',
            'title'         => 'test-title',
            'workflow'      => array(
                'state'     => 'review',
                'scheduled' => 'false'
            )
        );
        $this->request->setMethod('POST');
        $this->request->setPost($params);
        $this->dispatch('/content/edit/format/dojoio/id/' . $entry->getId());

        // ensure entry has not been modified
        $entry = P4Cms_Content::fetch($entry->getId());
        $this->assertSame(
            'published',
            $entry->getValue('workflowState'),
            "Expected workflow state has not been changed to review."
        );

        // ensure email has not been sent
        $sentEmails = $transport->getSentMails();
        $this->assertSame(
            0,
            count($sentEmails),
            "Expected sendEmail action has not occured."
        );
    }

    /**
     * Verify filtering content data grid by workflow states.
     */
    public function testFilterByWorkflow()
    {
        $this->utility->impersonate('administrator');

        $published = Workflow_Model_State::PUBLISHED;
        $time      = time() + 1000;

        // remove workflows/content types
        foreach (Workflow_Model_Workflow::fetchAll() as $workflow) {
            $workflow->delete();
        }
        foreach (P4Cms_Content_Type::fetchAll() as $type) {
            $type->delete();
        }

        // create workflows and content types with the following mapping:
        // --------------------------------------------------------
        // | CONTENT TYPE  | WORKFLOW  | STATES                   |
        // --------------------------------------------------------
        // | basic         | simple    | draft, published         |
        // | basic2        | simple    | draft, published         |
        // | simple        |           | -                        |
        // | private       | private   | draft                    |
        // | blog          | blog      | draft, review, published |
        // |               | unused    | draft, review, published |
        // --------------------------------------------------------
        Workflow_Model_Workflow::store(
            array(
                'id'            => "simple",
                'label'         => "simple workflow",
                'states'        => "[draft]\n"
                                .  "label=Draft\n"
                                .  "[$published]\n"
                                .  "label=Published"
            )
        );
        Workflow_Model_Workflow::store(
            array(
                'id'            => "private",
                'label'         => "private workflow",
                'states'        => "[draft]\n"
                                .  "label=Draft\n"
            )
        );
        Workflow_Model_Workflow::store(
            array(
                'id'            => "blog",
                'label'         => "blog workflow",
                'states'        => "[draft]\n"
                                .  "label=Draft\n"
                                .  "[review]\n"
                                .  "label=Review\n"
                                .  "[$published]\n"
                                .  "label=Published"
            )
        );
        Workflow_Model_Workflow::store(
            array(
                'id'            => "unused",
                'label'         => "unused workflow",
                'states'        => "[draft]\n"
                                .  "label=Draft\n"
                                .  "[review]\n"
                                .  "label=Review\n"
                                .  "[$published]\n"
                                .  "label=Published"
            )
        );
        P4Cms_Content_Type::store(
            array(
                'id'            => "basic",
                'label'         => "basic content type",
                'group'         => "misc",
                'workflow'      => "simple",
                'elements'      => "[title]\n"
                                .  "type=text"
            )
        );
        P4Cms_Content_Type::store(
            array(
                'id'            => "basic2",
                'label'         => "basic2 content type",
                'group'         => "misc",
                'workflow'      => "simple",
                'elements'      => "[title]\n"
                                .  "type=text"
            )
        );
        P4Cms_Content_Type::store(
            array(
                'id'            => "simple",
                'label'         => "simple content type",
                'group'         => "misc",
                'elements'      => "[title]\n"
                                .  "type=text"
            )
        );
        P4Cms_Content_Type::store(
            array(
                'id'            => "private",
                'label'         => "private content type",
                'group'         => "misc",
                'workflow'      => "private",
                'elements'      => "[title]\n"
                                .  "type=text"
            )
        );
        P4Cms_Content_Type::store(
            array(
                'id'            => "blog",
                'label'         => "blog content type",
                'group'         => "misc",
                'workflow'      => "blog",
                'elements'      => "[title]\n"
                                .  "type=text"
            )
        );

        // create contents with following relations to the workflows (dot denotes the same value as in the cell above):
        // -------------------------------------------------------------------------------------------------------
        // | CONTENT TITLE | CONTENT TYPE | WORKFLOW | STATE ASSIGNED | STATE SCHEDULED | STATE AS IT APPEARS    |
        // -------------------------------------------------------------------------------------------------------
        // | blog-draft    | blog         | blog     | draft          | -               | draft                  |
        // | blog-draft2   | .            | .        | draft          | review          | draft     -> review    |
        // | blog-review   | .            | .        | review         | published       | review    -> published |
        // | blog-pub      | .            | .        | published      | -               | published              |
        // | blog-abc      | .            | .        | abc            |                 | draft                  |
        // | simple        | simple       | -        | -              | -               | published              |
        // | simple-draft  | simple       | -        | draft          | review          | published              |
        // | simple2-draft | simple       | -        | draft          | published       | published              |
        // | basic-draft   | basic        | simple   | draft          | published       | draft     -> published |
        // | basic-pub     | .            | .        | published      | draft           | published -> draft     |
        // | basic-review  | .            | .        | review         | -               | draft                  |
        // | basic-xyz     | .            | .        | xyz            | -               | draft                  |
        // | basic2-draft  | basic2       | .        | draft          | -               | draft                  |
        // | private-draft | private      | private  | draft          | -               | draft                  |
        // | private-pub   | .            | .        | published      | -               | draft                  |
        // -------------------------------------------------------------------------------------------------------
        P4Cms_Content::store(
            array(
                'contentType'            => 'private',
                'title'                  => 'private-draft',
                'workflowState'          => 'draft'
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'private',
                'title'                  => 'private-pub',
                'workflowState'          => $published
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'basic2',
                'title'                  => 'basic2-draft',
                'workflowState'          => 'draft'
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'basic',
                'title'                  => 'basic-draft',
                'workflowState'          => 'draft',
                'workflowScheduledState' => $published,
                'workflowScheduledTime'  => $time
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'basic',
                'title'                  => 'basic-pub',
                'workflowState'          => $published,
                'workflowScheduledState' => 'draft',
                'workflowScheduledTime'  => $time
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'basic',
                'title'                  => 'basic-review',
                'workflowState'          => 'review'
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'basic',
                'title'                  => 'basic-xyz',
                'workflowState'          => 'xyz'
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'blog',
                'title'                  => 'blog-draft',
                'workflowState'          => 'draft'
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'blog',
                'title'                  => 'blog-draft2',
                'workflowState'          => 'draft',
                'workflowScheduledState' => 'review',
                'workflowScheduledTime'  => $time
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'blog',
                'title'                  => 'blog-pub',
                'workflowState'          => $published
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'blog',
                'title'                  => 'blog-review',
                'workflowState'          => 'review',
                'workflowScheduledState' => $published,
                'workflowScheduledTime'  => $time
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'blog',
                'title'                  => 'blog-abc',
                'workflowState'          => 'abc'
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'simple',
                'title'                  => 'simple',
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'simple',
                'title'                  => 'simple-draft',
                'workflowState'          => 'draft',
                'workflowScheduledState' => 'review',
                'workflowScheduledTime'  => $time,
            )
        );
        P4Cms_Content::store(
            array(
                'contentType'            => 'simple',
                'title'                  => 'simple2-draft',
                'workflowState'          => 'draft',
                'workflowScheduledState' => $published,
                'workflowScheduledTime'  => $time,
            )
        );

        // test json data for browse action when provided with various options for filtering by workflow
        $this->dispatch('/browse/format/json');
        $body     = $this->getResponse()->getBody();
        $data     = Zend_Json::decode($body);
        $expected = P4Cms_Content::fetchAll()->invoke('getTitle');

        $this->_verifyTitleValuesMatch($data['items'], $expected);

        // filter to keep only published
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'onlyPublished'
            ),
            array(
                array('blog-pub', 'simple', 'simple-draft', 'basic-pub', 'simple2-draft'),
                array('blog-review', 'basic-draft')
            )
        );

        // filter to keep only unpublished
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'onlyUnpublished'
            ),
            array(
                array('blog-draft', 'blog-draft2', 'blog-review', 'blog-abc', 'basic-draft',
                'basic-review', 'basic-xyz', 'basic2-draft', 'private-draft', 'private-pub'),
                array('blog-draft2', 'basic-pub')
            )
        );

        // filter to keep only items under simple workflow
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'userSelected',
                'states'    =>  array('simple/*')
            ),
            array(
                array('basic-draft', 'basic-pub', 'basic-review', 'basic-xyz', 'basic2-draft'),
                array('basic-draft', 'basic-pub')
            )
        );

        // filter to keep only items under blog workflow with published state
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'userSelected',
                'states'    =>  array('blog/' . $published),
            ),
            array(
                array('blog-pub'),
                array('blog-review')
            )
        );

        // filter to keep only items under blog workflow with draft state
        // or simple workflow with published state
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'userSelected',
                'states'    =>  array(
                    'blog/draft',
                    'simple/' . $published
                )
            ),
            array(
                array('blog-draft', 'blog-draft2', 'blog-abc', 'basic-pub'),
                array('basic-draft')
            )
        );

        // filter to keep only items under 'unused' workflow
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'userSelected',
                'states'    => array('unused/*'),
            ),
            array(
                array(),
                array()
            )
        );

        // filter to keep only items under private workflow having draft state
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'userSelected',
                'states'    =>  array('private/draft'),
            ),
            array(
                array('private-draft', 'private-pub'),
                array()
            )
        );

        // filter to keep only items under simple or private workflow with draft state
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'userSelected',
                'states'    =>  array(
                    'simple/draft',
                    'private/draft'
                )
            ),
            array(
                array('basic-draft', 'basic-review', 'basic-xyz', 'basic2-draft', 'private-draft', 'private-pub'),
                array('basic-pub')
            )
        );

        // if no selection is provided, ensure all items are shown
        $this->_verifyWorkflowFilter(
            array(
                'workflow'  => 'userSelected'
            ),
            array(
                P4Cms_Content::fetchAll()->invoke('getTitle'),
                P4Cms_Content::fetchAll()->invoke('getTitle')
            )
        );
    }

    /**
     * Test that proper icon for category is rendered (i.e. depending on category content).
     */
    public function testCategoryIcons()
    {
        // create entry in unpublished state
        $entry = $this->_makeContentEntry('draft', 'foo');

        // create category A with sub-category B containing the entry
        $categoryA = Category_Model_Category::store(
            array(
                'id'            => 'a',
                'title'         => 'A',
            )
        );
        $categoryB = Category_Model_Category::create(
            array(
                'id'            => 'a/b',
                'title'         => 'B',
            )
        )->addEntry($entry->getId())->save();

        // verify that member see icon representing empty folder for category B
        // as member cannot see unpublished content
        $this->utility->impersonate('member');

        $this->dispatch('/category/view/a');
        $this->assertQueryContentRegex("a[href='/category/view/a/b']", "/src=.+folder\-64x64/");

        // add access to unpublished content and verify category icon again
        $acl = P4Cms_Site::fetchActive()->getAcl();
        $acl->allow('member', 'content', array('access-unpublished'));

        $this->resetRequest()->resetResponse();
        $this->dispatch('/category/view/a');
        $this->assertQueryContentRegex("a[href='/category/view/a/b']", "/src=.+folder\-content\-64x64/");
    }

    /**
     * Helper function to verify filtering by workflow.
     * It automatically processes 3 cases - when filters are applied to the current,
     * scheduled or both states.
     *
     * Workflow data passed in the parameter are assumed to contain values from the
     * worfklow form, except 'targetState' that is added automatically.
     *
     * Expected entries are assumed to contain 2 or 3 lists of expected entry titles
     * when filters (represented by workflow data) are applied to the current state,
     * to the scheduled state and optionally to the both. If thirs list is omitted then
     * it will be calculated as union of the two for current and scheduled.
     *
     * @param   array   $workflowData       values of the workflow form representing filters.
     * @param   array   $expectedEntries    2- or 3-dimensional array containing lists
     *                                      of expected entry titles when filters are applied
     *                                      to the, in order, current state, scheduled state
     *                                      and both.
     */
    protected function _verifyWorkflowFilter(array $workflowData, array $expectedEntries)
    {
        $expectedEntries = array_values($expectedEntries);
        if (count($expectedEntries) === 2) {
            // add expected result for applied to 'current or scheduled' state as
            // union of the two
            $expectedEntries[] = array_merge(
                $expectedEntries[0],
                array_diff($expectedEntries[1], $expectedEntries[0])
            );
        }

        $targetStates = array('current', 'scheduled', 'either');
        foreach ($expectedEntries as $key => $expected) {
            // prepare post data and inject target state
            $postData                            = array('workflow' => $workflowData);
            $postData['workflow']['targetState'] = $targetStates[$key];

            // prepare request
            $this->resetRequest()->resetResponse();
            $this->request
                ->setMethod('post')
                ->setPost($postData);

            // dispatch
            $this->dispatch('/browse/format/json');
            $body     = $this->getResponse()->getBody();
            $data     = Zend_Json::decode($body);

            // verify response
            $this->_verifyTitleValuesMatch($data['items'], $expected);
        }
    }

    /**
     * Verifies that titles in $source array match values in $titleValues.
     *
     * @param   array   $source             array to compare with $titleValues, assuming array items contain
     *                                      'title' key
     * @param   array   $titleValues        array to compare to $source
     */
    protected function _verifyTitleValuesMatch(array $source, array $titleValues)
    {
        // extract title values from source
        $sourceTitle = array_map(
            function($item)
            {
                return isset($item['title']) ? $item['title'] : null;
            },
            $source
        );

        $match = (count($source) === count($titleValues))
            && count(array_diff($titleValues, $sourceTitle)) === 0;

        $this->assertTrue(
            $match,
            "Expected items after filtering by worfklow:"
            . "\nFiltered entry titles: " . print_r($sourceTitle, true)
            . "\nExpected entry titles: " . print_r($titleValues, true)
        );
    }

    /**
     * Create a test content entry in the given state and optionally set scheduled transition data.
     *
     * @param   string          $state          optional - the state to put the entry in.
     * @param   string          $owner          optional - username to set as entry owner.
     * @param   string          $scheduledState optional - state of the scheduled transition to
     *                                          set the entry on.
     * @param   string          $scheduledTime  optional - time of the scheduled transition to
     *                                          set the entry on.
     * @return  P4Cms_Content   created content entry instance
     */
    protected function _makeContentEntry($state = 'draft', $owner = '', $scheduledState = null,
        $scheduledTime = null
    )
    {
        return P4Cms_Content::store(
            array(
                'contentType'               => 'basic-page',
                'contentOwner'              => $owner,
                'title'                     => 'test-title',
                'body'                      => 'test-body',
                'workflowState'             => $state,
                'workflowScheduledState'    => $scheduledState,
                'workflowScheduledTime'     => $scheduledTime
            )
        );
    }

    /**
     * Verify that all expected states are present in the workflow sub-form as state radio options.
     * If selected state parameter is present, also ensure selected option.
     *
     * @param   array   $expectedStates     list with states for state radio element.
     * @param   string  $selectedState      state option assumed to be selected.
     */
    protected function _verifyWorkflowStatesRadio(array $expectedStates, $selectedState = null)
    {
        $statesImploded = implode(', ', $expectedStates);

        // verify number of radio options
        $expectedTransitionsCount = count($expectedStates);
        $this->assertQueryCount(
            '#workflow-state-element input[type="radio"]',
            $expectedTransitionsCount,
            "Expected state radio has " . $expectedTransitionsCount . " options: " . $statesImploded . "."
        );

        // verify radio option values
        $message = 'Expected states: [' . $statesImploded . ']';
        if ($selectedState) {
            $message .= ' with [' . $selectedState . '] selected';
        }
        foreach ($expectedStates as $state) {
            $query = '#workflow-state-element input[type="radio"][value="' . $state . '"]';
            if ($selectedState === $state) {
                $query .= '[checked="checked"]';
            }
            $this->assertQuery($query, $message);
        }
    }
}
# Change User Description Committed
#1 16170 perforce_software Move Chronicle files to follow new path scheme for branching.
//guest/perforce_software/chronicle/application/workflow/tests/ContentIntegrationTest.php
#1 8972 Matt Attaway Initial add of the Chronicle source code