<?php

loadBehavior
('Containable');

class 
ContainableBehaviorTest extends UnitTestCase {
    function 
setUp() {
        
$this->Containable =& new ContainableBehavior();
        
$this->User =& new User();
    }
    
    function 
testContain() {        
        
$tests = array(
            
=> array(
                
'Comment' => array('Post')
            )
            , 
=> array(
                
'Comment.Post'
            
)
            , 
=> array(
                
'Post' => array('Tag''User.Comment')
                , 
'Comment' => array('User')
                , 
'_checkEmpty' => false
            
)
            , 
=> array(
                
'Post.Tag'
            
)
            , 
=> array(
                
'Post.fields' => array('id''name')
            )
            , 
=> array(
                
'fields' => array('id')
            )
            , 
=> array(
                
'id''Post.fields' => array('id''created'), 'name''Post.Tag'
                
'_fields' => array
                (
                    
'User:hasMany:Post' => array('id''created')
                )
            )
            , 
=> array(
                
'id''Post.fields' => array('id''created'), 'name''Post.Tag' => array('name')
                , 
'_fields' => array
                (
                    
'User:hasMany:Post' => array('id''created')
                    , 
'Post:hasAndBelongsToMany:Tag' => array('name')
                )
            )
            , 
=> array(
                
'name''Comment' => array('id''Post''User' => array('id'))
                , 
'_fields' => array
                (
                    
'Comment:belongsTo:User' => array('id')
                    , 
'User:hasMany:Comment' => array('id''post_id''user_id')
                )
            )
        );
        
        
$testModel =& $this->User;
        
$testState = array('Post' => array('Comment.User''User.Comment''Tag'), 'Comment' => array('Post''User'), 'Group.User');
        foreach (
$tests as $test) {
            
$checkEmpty true;
            if (isset(
$test['_checkEmpty'])) {
                
$checkEmpty $test['_checkEmpty'];
                unset(
$test['_checkEmpty']);
            }
            
$fields null;
            if (isset(
$test['_fields'])) {
                
$fields $test['_fields'];
                unset(
$test['_fields']);
            }
            
$testModel->resetRecursively();
            
$this->assertAssocs($testModel$testStatefalse);
            
$containments $this->Containable->contain($testModel$test);
            
$this->assertAssocs($testModel$test$checkEmpty);
            
$this->assertIdentical($testModel->recursive$containments['_recursions']);
            
            if (
$fields !== null) {
                foreach (
$fields as $models => $containedFields) {
                    list(
$baseModel$assoc$assocModel) = explode(':'$models);
                    
$this->assertIdentical($containedFields$containments[$baseModel]['_instance']->{$assoc}[$assocModel]['fields']);
                }
            }
        }
    }
    
    function 
testAfterFind() {
        
$tests = array(
            
=> array(
                
'model' =>& $this->User
                
'containments' => array('Comment' => array('Post'))
            )
        );
        
        foreach (
$tests as $test) {
            
$containments $this->Containable->containments($test['model'], $test['containments']);
            foreach (
$test['containments'] as $model => $children) {
                
$this->assertAssoc($test['model'], $model);
                
$this->assertAssoc($test['model']->{$model}, $children);
                
$this->assertNoBackAssoc($test['model'], $model);
                
$this->assertNoBackAssoc($test['model']->{$model}, $children);
            }
            
        }

        
$this->assertAssoc($this->Usera('Comment''Group'));
        
$this->assertNoBackAssoc($this->Usera('Comment''Group'));
        
$this->User->unbindModel(array(
            
'hasMany' => array('Comment')
            , 
'belongsTo' => array('Group')
        ));
        
$this->assertNoAssoc($this->Usera('Comment''Group'));
        
$this->assertBackAssoc($this->Usera('Comment''Group'));
        
        
$this->Containable->runtime['User'] = $containments;
        
$this->Containable->afterFind($this->Usernulltrue);
        
        
$this->assertAssoc($this->Usera('Comment''Group'));
        
$this->assertNoBackAssoc($this->Usera('Comment''Group'));
    }
    
    function 
test_RequiredFields() {
        
// Test that field stays at null as nothing should be done here
        
$containments $this->Containable->contain($this->User, array('Comment' => array('Post')));
        
$r $this->Containable->_requiredFields($containments'Comment');
        
$this->assertIdentical($rnull);
        
        
// Try that we can fake _requiredFields into thinking the post_id needs to be included
        
$containments $this->Containable->contain($this->User, array('Comment' => array('Post')));
        
$r $this->Containable->_requiredFields($containments'Comment', array('id'));
        
$this->assertIdentical($r, array('id''post_id'));
        
        
// Test that the post_id field is added so Comment.Post can be fetched
        
$containments $this->Containable->contain($this->User, array('Comment' => array('Post''id')));
        
$r $this->Containable->_requiredFields($containments'Comment');
        
$this->assertIdentical($r, array('id''post_id'));
        
        
// Test that additional fields are not included for hasMany assocs
        
$containments $this->Containable->contain($this->User, array('Post.Comment.id'));
        
$r $this->Containable->_requiredFields($containments'Comment');
        
$this->assertIdentical($r, array('id'));
        
$r $this->Containable->_requiredFields($containments'Post');
        
$this->assertIdentical($rnull);
    }
    
    function 
testContainments() {
        
$tests = array(
            
=> array(
                
'containments' => array('Comment')
                , 
'expectations' => array(
                    
'User' => array('_instance' =>& $this->User'assocs' => array('Comment'))
                    , 
'Comment' => array('_instance' => & $this->User->Comment'assocs' => array())
                    , 
'_recursions' => 1
                
)
            )
            , 
=> array(
                
'containments' => array('Comment' => null)
            )
            , 
=> array(
                
'containments' => array('Comment' => array())
            )
            , 
=> array(
                
'containments' => array('Comment' => 'Post')
                , 
'args' => array('Comment''Comment.Post')
                , 
'expectations' => array(
                    
'User' => array('_instance' =>& $this->User'assocs' => array('Comment'))
                    , 
'Comment' => array('_instance' => & $this->User->Comment'assocs' => array('Post'))
                    , 
'Post' => array('_instance' =>& $this->User->Post'assocs' => array())
                    , 
'_recursions' => 2
                
)
            )
            , 
=> array(
                
'containments' => array('Comment' => array('Post'))
            )
            , 
=> array(
                
'containments' => array('Comment.Post')
            )
            , 
=> array(
                
'containments' => array('Comment.Post.Tag')
                , 
'expectations' => array(
                    
'User' => array('_instance' =>& $this->User'assocs' => array('Comment'))
                    , 
'Comment' => array('_instance' => & $this->User->Comment'assocs' => array('Post'))
                    , 
'Post' => array('_instance' =>& $this->User->Post'assocs' => array('Tag'))
                    , 
'Tag' => array('_instance' =>& $this->User->Post->Tag'assocs' => array())
                    , 
'_recursions' => 3
                
)
            )
            , 
=> array(
                
'containments' => array('Comment' => 'Post.Tag')
                , 
'args' => array('Comment''Comment.Post.Tag')
            )
            , 
=> array(
                
'containments' => array('Comment' => array('Post.Tag'))
            )
            , 
=> array(
                
'containments' => 'Comment.Post.Tag'
            
)            
            , 
10 => array(
                
'containments' => array('Comment.Post' => 'Tag''Post.Tag')
                , 
'expectations' => array(
                    
'User' => array('_instance' =>& $this->User'assocs' => array('Comment''Post'))
                    , 
'Comment' => array('_instance' => & $this->User->Comment'assocs' => array('Post'))
                    , 
'Post' => array('_instance' =>& $this->User->Post'assocs' => array('Tag'))
                    , 
'Tag' => array('_instance' =>& $this->User->Post->Tag'assocs' => array())
                    , 
'_recursions' => 3
                
)
            )
            , 
11 => array(
                
'containments' => array('Post.Tag''Comment''Group')
                , 
'expectations' => array(
                    
'User' => array('_instance' =>& $this->User'assocs' => array('Post''Comment''Group'))
                    , 
'Post' => array('_instance' =>& $this->User->Post'assocs' => array('Tag'))
                    , 
'Tag' => array('_instance' =>& $this->User->Post->Tag'assocs' => array())
                    , 
'Comment' => array('_instance' =>& $this->User->Comment'assocs' => array())
                    , 
'Group' => array('_instance' =>& $this->User->Group'assocs' => array())
                    , 
'_recursions' => 2
                
)
            )
            , 
12 => array(
                
'containments' => array('Comment' => array('fields' => array('id''text')))
                , 
'expectations' => array(
                    
'User' => array('_instance' =>& $this->User'assocs' => array('Comment'))
                    , 
'Comment' => array('_instance' => & $this->User->Comment'assocs' => array(), 'fields' => array('id''text'))
                    , 
'_recursions' => 1
                
)
            )
            , 
13 => array(
                
'containments' => array('Comment' => array('fields' => array('id''text')), 'Post.Comment' => array('fields' => array('id''created')))
                , 
'expectations' => array(
                    
'User' => array('_instance' =>& $this->User'assocs' => array('Comment''Post'))                    
                    , 
'Comment' => array('_instance' => & $this->User->Comment'assocs' => array(), 'fields' => array('id''text''created'))                    
                    , 
'Post' => array('_instance' =>& $this->User->Post'assocs' => array('Comment'))
                    , 
'_recursions' => 2
                
)
            )
            , 
14 => array(
                
'containments' => array('fields' => array('id''name'), 'Comment' => array('fields' => array('id''text'), 'User' => array('fields' => array('modified'))), 'Post.Comment' => array('fields' => array('id''created')))
                , 
'expectations' => array(
                    
'User' => array('_instance' =>& $this->User'assocs' => array('Comment''Post'), 'fields' => array('modified'))
                    , 
'Comment' => array('_instance' => & $this->User->Comment'assocs' => array('User'), 'fields' => array('id''text''created'))                    
                    , 
'Post' => array('_instance' =>& $this->User->Post'assocs' => array('Comment'))
                    , 
'_query' => array('fields' => array('id''name'))
                    , 
'_recursions' => 2
                
)
            )
            , 
15 => array(
                
'containments' => array('id''name''Comment' => array('id''text''User' => array('modified')), 'Post.Comment' => array('id''created'))
            )
            , 
16 => array(
                
'containments' => array('id''name''Comment' => array('id''text'), 'Comment.User' => array('modified'), 'Post.Comment' => array('id''created'))
            )
            , 
17 => array(
                
'containments' => array('id''name''Comment.fields' => array('id''text'), 'Comment.User.fields' => array('modified'), 'Post.Comment' => array('id''created'))
            )
        );
        
        foreach (
$tests as $i => $test) {
            if (isset(
$test['expectations'])) {
                
$expectations $test['expectations'];
            }
            if (
$test['containments']) {
                
Configure::write('Test.nr'$i);
                
$r $this->Containable->containments($this->User$test['containments']);
                
$this->assertTrue($r === $expectations'Executing test #'.$i.'.');
                if (
$r !== $expectations) {
                    
// Some code to debug stuff when things go wrong and assertIdentical fails cause of deep nesting
                    
if (array_keys($r) !== array_keys($expectations)) {
                        
debug('Wrong Model keys.');
                    }
                    foreach (
$r as $model => $containments) {
                        if (!
in_array($model, array('_recursions''_query'))) {
                            if (
$expectations[$model]['_instance'] !== $r[$model]['_instance']) {
                                
debug('Model "'.$model.'": Wrong instance.');
                            }
                            
                            if (
array_keys($expectations[$model]) !== array_keys($r[$model])) {
                                
debug('Model "'.$model.'": Wrong keys.');
                            }
                            
                            if (@
$expectations[$model]['fields'] !== @$r[$model]['fields']) {
                                
debug('Model "'.$model.'": Wrong fields.');
                            }
                            
                            if (
$expectations[$model]['assocs'] !== $r[$model]['assocs']) {
                                
debug('Model "'.$model.'": Wrong Assocs.');
                                
debug($expectations[$model]['assocs']);
                                
debug($r[$model]['assocs']);
                            }
                        } else {
                            if (
$expectations['_recursions']  !== $r['_recursions']) {
                                
debug('Wrong assoc level.');
                            }
                        }
                    }
                    
                    
$this->assertIdentical($r$expectations);
                }
            }
            if (isset(
$test['args'])) {
                
$args $test['args'];
                
array_unshift($args$this->User);                
                
$r call_user_func_array(array(&$this->Containable'containments'), $args);
                
                
$this->assertTrue($r === $expectations'Executing test #'.$i.' (args).');
            }
        }
    }
    
    function 
test_MergeFields() {
        
$tests = array(
            
=> array(
                
'args' => array(array(), array('id'))
                , 
'expectations' => array('id')
            ),
            
=> array(
                
'args' => array(array('id'), array('text'))
                , 
'expectations' => array('id''text')
            ),
            
=> array(
                
'args' => array(array('id'), array('id''text'))
                , 
'expectations' => array('id''text')
            )
        );
        
        foreach (
$tests as $i => $test) {
            if (isset(
$test['expectations'])) {
                
$expectations $test['expectations'];
            }
            
            
$r call_user_func_array(array(&$this->Containable'_mergeFields'), $test['args']);
            
$this->assertTrue($r === $expectations'Executing test #'.$i.'.');
        }
    }
    
    function 
assertAssocs(&$model$assocs$checkEmpty true$__rootLevel true) {
        static 
$parentModels;
        
        if (!
is_bool($__rootLevel)) {
            
$__rootLevel true;
        }
        
        if (
$__rootLevel == true) {
            
$parentModels = array();
        }
        
        
$parent =& array_shift($parentModels);
        
        
$topAssocs = array();
        foreach ((array)
$assocs as $name => $children) {
            if (
is_numeric($name)) {
                
$name $children;
                
$children = array();
            }
            
            if (
strpos($name'.') !== false) {
                
$chain explode('.'$name);
                
$name array_shift($chain);
                
$children = array(join('.'$chain) => $children);
            }        
            
            if (
in_array($name, array('fields'), true)) {
                if (
is_object($parent)) {
                    
$this->assertAssocSettings($parent$model->name, array($name => $children));
                }
                continue;
            }
            
            
$t $this->assocExists($model$name);
            if (
$t == false) {
                if (
$model->hasField($name)) {
                    return 
true;
                }
                
$this->assertTrue(false'Association check failed. Model "'.$model->name.'" is not associated with Model "'.$name.'".');
                return 
false;
            }
            
            
$topAssocs[] = $name;
            
$parentModels[] =& $model;
                        
            
$r $this->assertAssocs($model->{$name}, $children$checkEmptyfalse);
            if (
$r == false) {
                return 
false;
            }
        }
        
        if (
$checkEmpty && empty($topAssocs)) {
            foreach (
array_keys($model->__associationKeys) as $assocType) {
                foreach (
$model->{$assocType} as $assocModel => $assocCfg) {
                    if (!
in_array($assocModel$topAssocs)) {
                        
$this->assertTrue(false'Model "'.$model->name.'" has an unexpected "'.$assocType.'" association with Model "'.$assocModel.'"');
                        return 
false;
                    }
                }
            }
        }
        
        if (
$__rootLevel == true) {
            
$this->assertTrue(true'Checking assocs for model "'.$model->name.'".');
        }
        
        return 
true;
    }
        
    function 
assertAssoc(&$model$assocModel$type null$assert 'assertTrue') {
        
$assocModels = (array)$assocModel;
        foreach (
$assocModels as $assocModel) {
            
$r $this->assocExists($model$assocModel$type);
            
$this->{$assert}($r'Asserting that model "'.$model->name.'" is '.ife($assert == 'assertFalse''not ').'associated with "'.$assocModel.'"'.ife($type' via "'.$type.'".''.'));
        }
    }
    
    function 
assertBackAssoc(&$model$assocModel$type null$assert 'assertTrue') {
        
$assocModels = (array)$assocModel;
        foreach (
$assocModels as $assocModel) {
            
$r $this->assocExists($model$assocModel$type'backAssociation');
            return 
$this->{$assert}($r'Asserting that model "'.$model->name.'" was '.ife($assert == 'assertFalse''not ').'associated with "'.$assocModel.'"').ife($type' via "'.$type.'".''.');
        }
    }
    
    function 
assertNoAssoc(&$model$assocModel$type null) {
        
$this->assertAssoc($model$assocModel$type'assertFalse');
    }
    
    function 
assertNoBackAssoc(&$model$assocModel$type null) {
        
$this->assertBackAssoc($model$assocModel$type'assertFalse');
    }
    
    function 
assocExists(&$model$assocModel$type null$where null) {
        if (!empty(
$type)) {
            if (empty(
$where)) {
                return isset(
$model->{$type}[$assocModel]);
            } else {
                return isset(
$model->{'__'.$where}[$type][$assocModel]);
            }
        }
        
        foreach (array(
'belongsTo''hasMany''hasOne''hasAndBelongsToMany') as $type) {
            if (empty(
$where)) {
                if (isset(
$model->{$type}[$assocModel])) {
                    return 
true;
                }
            } else {
                if (isset(
$model->{'__'.$where}[$type][$assocModel])) {
                    return 
true;
                }
            }
        }
        
        return 
false;
    }
    
    function 
assertAssocSettings(&$model$assocModel$settings$type null) {
        if (!empty(
$type)) {
            
$assocSettings $model->{$type}[$assocModel];
        } else {
            foreach (array(
'belongsTo''hasMany''hasOne''hasAndBelongsToMany') as $type) {
                if (isset(
$model->{$type}[$assocModel])) {
                    
$assocSettings $model->{$type}[$assocModel];
                    break;
                }
            }
        }
                
        
$overlapSettings array_intersect_key($assocSettings$settings);
        
        
$this->assertIdentical($settings$overlapSettings);
        
        return 
false;
    }
}

class 
ContainableTestModel extends CakeTestModel {
    var 
$useTable false;
    
    function 
__construct($id false$table null$ds null) {
        
parent::__construct($id$table$ds);
        
        
$this->table Inflector::tableize($this->name);
        
$this->__createLinks();
    }
    
    function 
hasField($field) {
        return 
true;
    }