#! /usr/bin/env python
# -*- coding: iso-8859-1 -*-
#-----------------------------------------------------------------------------
# Modeling Framework: an Object-Relational Bridge for python
#
# Copyright (c) 2001-2004 Sbastien Bigaret <sbigaret@users.sourceforge.net>
# All rights reserved.
#
# This file is part of the Modeling Framework.
#
# This code is distributed under a "3-clause BSD"-style license;
# see the LICENSE file for details.
#-----------------------------------------------------------------------------
"""
Tests for PyModel
"""
import unittest
if __name__ == "__main__":
import utils, sys
utils.fixpath()
from Modeling.PyModel import *
class TestPyModel(unittest.TestCase):
"""
Tests for PyModel
"""
def checkEntitiesProperties(self, entities):
"""
For each entity in 'entities', check that its attributes and
relationships entity() points to it. Also check that the relationships'
destination entity is not None.
See in particular the comment in updateComponent() for an explanation
"""
for e in entities:
for a in e.attributes():
self.assertEqual(a.entity(), e)
for r in e.relationships():
self.assertEqual(r.entity(), e)
self.failUnless(r.destinationEntity(),'%s.%s.destinationEntity() is None'%(e.name(),r.name()))
def check_model(self, model):
"""
This method tests that:
- checkEntitiesProperties() succeeds,
- SalesClerk has 2 relationships,
- for Employee and SalesClerk, relationships 'toStore' and 'toMarks'
have the correct multiplicity, that their source and destination
attributes are correct, and that their destination entities are correct
"""
emp=model.component.entityNamed('Employee')
sc=model.component.entityNamed('SalesClerk')
store=model.component.entityNamed('Store')
mark=model.component.entityNamed('Mark')
self.checkEntitiesProperties((emp,sc,store,mark))
self.assertEqual(len(sc.relationships()), 2)
def check(self, toStore, toMarks, store, mark):
self.failUnless(toStore.isToOne())
self.failUnless(toMarks.isToMany())
self.assertEqual(toStore.destinationEntity(), store)
self.assertEqual(toMarks.destinationEntity(), mark)
# Check toStore and toMarks for Employee
toStore=emp.propertyNamed('toStore')
fkStore=emp.propertyNamed('fkStore')
toMarks=emp.propertyNamed('toMarks')
check(self, toStore, toMarks, store, mark)
self.failIf(fkStore not in toStore.sourceAttributes())
self.failIf(store.propertyNamed('id') not in toStore.destinationAttributes())
self.failIf(emp.propertyNamed('id') not in toMarks.sourceAttributes())
self.failIf(mark.propertyNamed('fkEmployee') not in toMarks.destinationAttributes())
self.failIf(fkStore.isRequired()!=toStore.multiplicityLowerBound(), '%i != %i'%(fkStore.isRequired(),toStore.multiplicityLowerBound()))
# Check toStore and toMarks for SalesClerk
toStore=sc.propertyNamed('toStore')
fkStore=sc.propertyNamed('fkStore')
toMarks=sc.propertyNamed('toMarks')
check(self, toStore, toMarks, store, mark)
self.failIf(sc.propertyNamed('fkStore') not in toStore.sourceAttributes())
self.failIf(store.propertyNamed('id') not in toStore.destinationAttributes())
self.failIf(sc.propertyNamed('id') not in toMarks.sourceAttributes())
self.failIf(mark.propertyNamed('fkEmployee') not in toMarks.destinationAttributes())
self.failIf(fkStore.isRequired()!=toStore.multiplicityLowerBound())
def check_inverse_rels(self, model):
"""
This method checks that Employee.toStore (resp. Employee.toMarks) is the
inverse relationship for Store.toEmployees (resp. Mark.toEmployee)
"""
emp=model.component.entityNamed('Employee')
sc=model.component.entityNamed('SalesClerk')
store=model.component.entityNamed('Store')
mark=model.component.entityNamed('Mark')
self.failUnless(mark.relationshipNamed('toEmployee').inverseRelationship()==emp.propertyNamed('toMarks'))
self.failUnless(store.relationshipNamed('toEmployees').inverseRelationship()==emp.propertyNamed('toStore'))
toEmployee=mark.relationshipNamed('toEmployee')
self.failIf(mark.propertyNamed('fkEmployee').isRequired()!=toEmployee.multiplicityLowerBound())
## BEGIN tests
def setUp(self):
self.model = Model('StoreEmployees',adaptorName='Postgresql')
self.model.version='0.1'
Entity.defaults['properties'] = [
APrimaryKey('id', isClassProperty=0, isRequired=1, doc='Default PK')
]
AString.defaults['width'] = 20
def test_01_attr(self):
"[PyModel] entity w/ attributes and default pk"
model = self.model
model.entities = [
Entity('Employee',
properties=[ AString('lastName', isRequired=1, usedForLocking=1),
AString('firstName', isRequired=1, width=50,
usedForLocking=1),
] ),
]
model.build()
emp=model.component.entityNamed('Employee')
self.checkEntitiesProperties((emp,))
self.assertEqual(emp.attributeNamed('lastName').width(), 20)
self.assertEqual(emp.attributeNamed('firstName').width(), 50)
self.assertEqual(emp.attributeNamed('firstName').isRequired(), 1)
self.assertEqual(emp.primaryKeyAttributeNames(), ['id']) # default PK
self.failIf(emp.primaryKeyAttributes()[0].isClassProperty())
self.assertEqual(emp.primaryKeyAttributes()[0].defaultValue(), None)
def test_01b_attr(self):
"[PyModel] attributes' props can be reassigned before build()"
# bug #842698
model = self.model
model.entities = [
Entity('Employee', properties=[ AString('firstName', width=30) ] ),
]
attr=model.entityNamed('Employee').attributeNamed('firstName')
attr.width=50
attr.isRequired=1
attr.usedForLocking=1
model.build()
emp=model.component.entityNamed('Employee')
self.checkEntitiesProperties((emp,))
firstName=emp.attributeNamed('firstName')
self.assertEqual(firstName.width(), 50)
self.assertEqual(firstName.isRequired(), 1)
self.failUnless(firstName in emp.attributesUsedForLocking())
def test_02_pk(self):
"[PyModel] entity w/ pk"
model = self.model
# we also use the same name 'id'
model.entities = [
Entity('Employee',
properties=[ APrimaryKey('id', isClassProperty=1, isRequired=1,
doc='SalesClerk PK') ] ), ]
model.build()
emp=model.component.entityNamed('Employee')
self.checkEntitiesProperties((emp,))
self.assertEqual(emp.primaryKeyAttributeNames(), ['id'])
self.assertEqual(emp.primaryKeyAttributes()[0].comment(), 'SalesClerk PK')
self.failUnless(emp.primaryKeyAttributes()[0].isClassProperty())
self.assertEqual(emp.primaryKeyAttributes()[0].defaultValue(), 0)
def test_03_attrs_and_inheritance(self):
"[PyModel] attrs_and_inheritance"
model = self.model
model.entities = [
Entity('Employee',
properties=[ AString('lastName', isRequired=1, usedForLocking=1),
AString('firstName', isRequired=1, width=50,
usedForLocking=1),
] ),
Entity('SalesClerk', parent='Employee',
properties=[ APrimaryKey('id', isClassProperty=1, isRequired=1,
# NB: avec ID & Rels c'est une erreur!
doc='SalesClerk PK'),
AString('firstName', isRequired=1, width=5,
usedForLocking=1),
] ),
Entity('Executive', parent='Employee',
properties=[ AString('officeLocation', width=5), ] ),
]
model.build()
emp=model.component.entityNamed('Employee')
sc=model.component.entityNamed('SalesClerk')
ex=model.component.entityNamed('Executive')
self.checkEntitiesProperties((emp,sc,ex))
self.assertEqual(emp.primaryKeyAttributeNames(), ['id'])
self.assertEqual(emp.primaryKeyAttributeNames(), ['id']) # PK overriden
self.assertEqual(emp.primaryKeyAttributeNames(), ['id'])
self.failIf(emp.primaryKeyAttributes()[0].isClassProperty())
self.failIf(ex.primaryKeyAttributes()[0].isClassProperty())
self.failUnless(sc.primaryKeyAttributes()[0].isClassProperty())
# Propagation of attributes
self.failIf([a for a in emp.attributesNames()
if a not in sc.attributesNames()])
self.failIf([a for a in emp.attributesNames()
if a not in ex.attributesNames()])
self.assertEqual(len(sc.attributes()),3)
self.assertEqual(len(ex.attributes()),4)
#
self.assertEqual(emp.attributeNamed('firstName').width(), 50)
self.assertEqual(sc.attributeNamed('firstName').width(), 5)
self.assertEqual(emp.attributeNamed('id').entity(), emp)
self.assertEqual(sc.attributeNamed('id').entity(), sc)
self.assertEqual(ex.attributeNamed('id').entity(), ex)
def test_04_fully_qualified_relationships_no_inverse(self):
"[PyModel] fully_qualified_relationships"
model = self.model
model.entities = [
Entity('Employee',
properties=[ AForeignKey('fkStore'),
RToOne('toStore', 'Store',src='fkStore',dst='id',
delete='deny'),
RToMany('toMarks', 'Mark',src='id',dst='fkEmployee',
delete='cascade'),
] ),
Entity('SalesClerk', parent='Employee'),
Entity('Mark', properties=[ AForeignKey('fkEmployee') ]),
Entity('Store'),
]
model.build()
self.check_model(model)
self.failIf(model.component.entityNamed('Employee').propertyNamed('toStore').deleteRule()!='deny')
self.failIf(model.component.entityNamed('Employee').propertyNamed('toMarks').deleteRule()!='cascade')
def test_05_unqualified_relationships_no_inverse(self):
"[PyModel] unqualified_relationships_no_inverse"
model = self.model
model.entities = [
Entity('Employee',
properties=[
RToMany('toMarks', 'Mark'),
RToOne('toStore', 'Store'),
] ),
Entity('SalesClerk', parent='Employee'),
Entity('Mark'),
Entity('Store'),
]
model.build()
self.check_model(model)
emp=model.component.entityNamed('Employee')
sc=model.component.entityNamed('SalesClerk')
store=model.component.entityNamed('Store')
mark=model.component.entityNamed('Mark')
self.assertEqual(len(sc.relationships()), 2)
# Check that each entity has the expected number of attributes
self.assertEqual(len(emp.attributes()), 2) # id + fk
self.assertEqual(len(sc.attributes()), 2) # id + fk
self.assertEqual(len(store.attributes()), 1) # id
self.assertEqual(len(mark.attributes()), 2) # id + fk
self.failUnless(emp.attributeNamed('fkStore'))
self.failUnless(sc.attributeNamed('fkStore'))
self.failUnless(mark.attributeNamed('fkEmployee'))
# test_06 qualified & unqualified
# model = self.model
# model.entities = [
# Entity('Employee',
# properties=[ AForeignKey('fkStore'),
# #RToOne('toStore', 'Store',src='fkStore',dst='id'),
# RToMany('toMarks', 'Mark',src='id',dst='fkEmployee'),
# ] ),
# Entity('SalesClerk', parent='Employee'),
# Entity('Mark',
# properties=[ AForeignKey('fkEmployee'),
# #RToOne('toEmployee','Employee',inverse='toMarks')
# ]
# ),
# Entity('Store',
# properties=[RToMany('toEmployees','Employee')#,src='id',dst='fkStore')
# ]#,inverse='toStore')]
# ),
# # NB: quid si inverse='ghjgh'? --> TBD test!
# ]
def test_07_relationships_with_inverse(self):
"[PyModel] qualified_relationships_with_inverse"
self.model.entities = [
Entity('Employee',
properties=[ AForeignKey('fkStore'),
RToOne('toStore', 'Store',src='fkStore',dst='id'),
RToMany('toMarks', 'Mark',src='id',dst='fkEmployee'),
] ),
Entity('SalesClerk', parent='Employee'),
Entity('Mark',
properties=[ AForeignKey('fkEmployee'),
RToOne('toEmployee','Employee',inverse='toMarks')
]
),
Entity('Store',
properties=[RToMany('toEmployees','Employee',inverse='toStore',
)#src='id',dst='fkStore')
]#,inverse='toStore')]
),
# NB: quid si inverse='ghjgh'? --> TBD test!
]
self.model.build()
self.check_model(self.model)
self.check_inverse_rels(self.model)
#
self.setUp()
self.model.entities = [
Entity('Employee',
properties=[ RToOne('toStore', 'Store'),#,src='fkStore',dst='id'),
RToMany('toMarks', 'Mark'),#,src='id',dst='fkEmployee'),
] ),
Entity('SalesClerk', parent='Employee'),
Entity('Mark',
properties=[ RToOne('toEmployee','Employee',inverse='toMarks') ]
),
Entity('Store',
properties=[RToMany('toEmployees','Employee',inverse='toStore',)]
),
]
self.model.build()
self.check_model(self.model)
self.check_inverse_rels(self.model)
# Automatic creation of source + destination key
# NOTE: this fails by now, because the feature is considered not desirable
# for now.
self.setUp()
self.model.entities = [
Entity('Employee',
properties=[ RToOne('toStore', 'Store',src='fkStore',dst='id'),
RToMany('toMarks', 'Mark',src='id',dst='fkEmployee'),
] ),
Entity('SalesClerk', parent='Employee'),
Entity('Mark',
properties=[ RToOne('toEmployee','Employee',inverse='toMarks') ]
),
Entity('Store',
properties=[RToMany('toEmployees','Employee',inverse='toStore',)]
),
]
self.assertRaises(ValueError, self.model.build)
#self.check_model(self.model)
#self.check_inverse_rels(self.model)
def test_08_qualified_relationships_with_inverse(self):
"[PyModel] unqualified_relationships_with_inverse"
# Simple declaration
model = self.model
model.entities = [
Entity('Employee'),
Entity('SalesClerk', parent='Employee'),
Entity('Mark'),
Entity('Store'),
]
model.associations = [
Association('Employee', 'Store'),
Association('Mark', 'Employee'),
]
model.build()
self.check_model(self.model)
self.check_inverse_rels(self.model)
#
self.setUp()
model=self.model
model.entities = [
Entity('Employee'),
Entity('SalesClerk', parent='Employee'),
Entity('Mark'),
Entity('Store'),
]
model.associations = [
Association('Employee', 'Store',
relations=['toStore', 'toEmployees'],
multiplicity=[ [1, 1], [0, None] ],
keys=['fkStore', 'id'],
delete=['nullify', 'deny'],
),
Association('Mark', 'Employee',
relations=['toEmployee', 'toMarks'],
multiplicity=[ [1, 1], [0, None] ],
keys=['fkEmployee', 'id'],
delete=['nullify', 'cascade']
),
]
model.build()
self.check_model(model)
self.check_inverse_rels(model)
# check that delete rules were correctly set
emp=model.component.entityNamed('Employee')
mark=model.component.entityNamed('Mark')
store=model.component.entityNamed('Store')
# check that the fk are set as expected
self.failUnless(emp.propertyNamed('fkStore'))
self.failUnless(mark.propertyNamed('fkEmployee'))
self.failIf(emp.propertyNamed('toStore').deleteRule()!='nullify')
self.failUnless(emp.propertyNamed('toStore').isMandatory()) #841315
self.failIf(emp.propertyNamed('toMarks').deleteRule()!='cascade')
self.failIf(store.propertyNamed('toEmployees').deleteRule()!='deny')
self.failIf(mark.propertyNamed('toEmployee').deleteRule()!='nullify')
self.failUnless(mark.propertyNamed('toEmployee').isMandatory())
# Check Association's defaults
ass_def_delete=Association.defaults['delete']
try:
Association.defaults['delete']=['deny', 'cascade']
self.setUp()
model=self.model
model.entities = [ Entity('Employee'), Entity('Mark'), Entity('Store'),
Entity('SalesClerk', parent='Employee'), ]
model.associations = [
Association('Employee', 'Store'), Association('Mark', 'Employee'), ]
model.build()
self.check_model(model)
self.check_inverse_rels(model)
emp=model.component.entityNamed('Employee')
mark=model.component.entityNamed('Mark')
store=model.component.entityNamed('Store')
self.failIf(emp.propertyNamed('toStore').deleteRule()!='deny')
self.failIf(emp.propertyNamed('toMarks').deleteRule()!='cascade')
self.failIf(store.propertyNamed('toEmployees').deleteRule()!='cascade')
self.failIf(mark.propertyNamed('toEmployee').deleteRule()!='deny')
finally:
Association.defaults['delete']=ass_def_delete # restore
# Association's defaults are NOT related to RToOne/RToMany defaults
toOne_def_delete=RToOne.defaults['delete']
toMany_def_delete=RToMany.defaults['delete']
RToOne.defaults['delete']=['deny']
RToOne.defaults['delete']=['cascade']
try:
self.setUp()
model=self.model
model.entities = [ Entity('Employee'), Entity('Mark'), Entity('Store'),
Entity('SalesClerk', parent='Employee'), ]
model.associations = [
Association('Employee', 'Store'), Association('Mark', 'Employee'), ]
model.build()
self.check_model(model)
self.check_inverse_rels(model)
emp=model.component.entityNamed('Employee')
mark=model.component.entityNamed('Mark')
store=model.component.entityNamed('Store')
self.failIf(emp.propertyNamed('toStore').deleteRule()=='deny')
self.failIf(emp.propertyNamed('toMarks').deleteRule()=='cascade')
self.failIf(store.propertyNamed('toEmployees').deleteRule()=='cascade')
self.failIf(mark.propertyNamed('toEmployee').deleteRule()=='deny')
finally:
RToOne.defaults['delete']=toOne_def_delete
RToOne.defaults['delete']=toMany_def_delete
def test_09_cannot_rebuild(self):
"[PyModel] build() should be called once, no more"
model = self.model
model.entities = [
Entity('Employee',
properties=[ AString('lastName'),
AString('firstName'),
] ),
]
model.build()
self.failUnless(model.is_built)
self.assertRaises(RuntimeError, model.build)
def test_10_bound_relationships(self):
"[PyModel] bug #1023234-relationships with an finite upper bound"
model = self.model
model.entities = [
Entity('Employee'), Entity('Store'),
]
model.associations = [ Association('Employee', 'Store',
multiplicity = [ [0,1], [0,2] ] )
]
model.build()
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestPyModel, "test_"))
return suite
if __name__ == "__main__":
verbose='-V' in sys.argv and 'Y' or ('-v' in sys.argv and 1 or 0)
errs = utils.run_suite(test_suite(), verbosity=verbose)
sys.exit(errs and 1 or 0)
## TBD: test init/build sur elements, p.ex. RToOne + cardinality, etc.
|