Hi,

First of all, thanks for all the hard work and quality that goes into 
Pyramid to Chris and his team!

At my job, we're using Pyramid to do a massive migration from both legacy 
ASP code and also Pylons code.

I'm doing some unit and functional tests on my views and I have a couple of 
questions.

First, for all of its benefits, I've sometimes found the Zope Transaction 
Manager to be a big pain at times, but a lot of code has been assuming it's 
use, so for better or worse, we're stuck with it. The first issue relates 
to using WebTest for full-framework functional testing: how does one get 
session objects attached to the request object when WebTest (TestApp) uses 
WebOb's request object and not Pyramid's request object and all of its 
goodies? Will I have to subclass WebTest objects and add Pyramid features? 
Is there an easier way? For now, I went with attaching a session dict to a 
DummyRequest() testing object and just calling view code ala a simple unit 
test...but...for certain views that brings me to problem number two:

SQLAlchemy appears to complain about a scoped session already existing, and 
throws a strange error on the tearDown() of a bit of data that happens 
within the view. It seems to be trying to UPDATE (during the tearDown()) 
the table that gets INSERTED to within the view code being called by the 
unit test. The unit test of course sets upand tears down its own tables and 
data. I have no problem with such things when views under unit test are 
doing simple queries, but once they start to INSERT and such, SQLAlchemy 
refuses to behave. I've tried countless options, and spent literally hours 
working around SQLAlchemy and the Zope TM, but in this particular case, I'm 
stuck and could use some other people's insight.

Here's some code to show what's going on, followed by errors reported:

from tests/__init__.py:

def _initTestingDB():
    from sqlalchemy import create_engine
    from pyramid_helios.models.meta import (
        DBSession,
        Base
        )
    engine = create_engine('sqlite://')
    Base.metadata.create_all(engine)
    DBSession.configure(bind=engine)
    return engine, DBSession

class UnitTest(unittest.TestCase):
    ''' Base class for tests '''
    def setUp(self):
        template_dir = os.path.join(os.path.dirname(
                                    
os.path.dirname(os.path.abspath(__file__))),
                                    'templates')
        self.config = testing.setUp(settings={'production' : 'false',
                                              'test_mode' :  'true',
                                              'letters.pdf.formsdir' 
:'$(here)s/forms',
                                              'notepad_provider' : 'null',
                                              'mako.directories' : 
template_dir,
                                              'email_to_test' : 
'm...@mycompany.com',
                                              'email_to' : 
'webt...@mycompany.com',
                                              'email.paperless.name' : 
'alert thing'
                                              'email.paperless.address' : 
'testale...@mycompany.com' })
        self.config.include('pyramid_helios.lib.email.testing')
        self.engine, self.session = _initTestingDB()
        from pyramid_helios import models
        self.env = models
        self.dbfixture = SQLAlchemyFixture(
                             env=models,
                             engine=self.engine,
                             style=NamedDataStyle()
                             )
    def tearDown(self):
        ''' Clears away any existing objects between tests. '''
        self.session.remove()
        testing.tearDown()


here's the view I'm testing:
@view_config(route_name='payment_cc_setup')
def payment_cc_setup(request):
    ''' This is the popup response to registering a card.
    The contents will be displayed within a 'fancybox' JavaScript popup'''
    #### needed variables from the request and session:
    session = request.session
    refn = session['refn']
    log.debug('refn from session is %s' % refn)
    if request.params['Result'] == 'ERROR':
        message = request.params['MESSAGE']
        return Response('''We detect the presence of an error. The message 
sent was: <br><br> %s''' % message)
    else:
        rrno = request.params['RRNO']
        description = request.params['CARD_TYPE'] + ' ending in ' + 
request.params['PAYMENT_ACCOUNT']
        cc_exp_date = request.params['CARD_EXPIRE']

        transaction.get()
        ses = DBSession()
        ses.begin(subtransactions=True)
        ses.add(SavedAccts(refnum='2234123412',
                           rrno=rrno,
                           description=description,
                           cc_exp_date=cc_exp_date,
                           full_name='',
                           transactiontime=datetime.datetime.now()))
        ses.flush()

        return Response('''You have successfully set up a new account into 
your stored accounts database.
You may now use the dropdown list to make a payment against your 
loan(s).''')

NOTE: I've tried different permutations of INSERT in various SQLAlchemy 
styles: non-scoped sessions, just using the connection itself and doing a 
raw query using 'text' in SQLAlchemy (this works, but other things don't 
work then in the unittest....

and here's the unit test. As you can see I've tried various ways of setting 
up the verification that the view's insert went ok, but when they 'work', 
the tearDown throws an error (see below):

    def test_payment_cc_setup_approved(self):
        req = DummyRequest()
        req.session = {'refn' : '2234123412'}
        req.method = 'POST'
        req.params = {'Result' : 'Approved',
                      'MESSAGE' : 'Good job!',
                      'RRNO' : '100198989797',
                      'CARD_TYPE' : 'VISA',
                      'PAYMENT_ACCOUNT' : 'xxxxxxxxxxxx4444',
                      'CARD_EXPIRE' : '0317'}
        resp = payment_cc_setup(req)
        self.assertIn('You may now', resp.body)
        #self.session.begin(subtransactions=True)
        #q = 
self.session.query(SavedAccts).filter(SavedAccts.refnum=='2234123412').all()
        q = ''' SELECT * from borr_saved_accts WHERE refnum=='2234123412' 
'''
        rows = self.session.connection().execute(q).fetchall()
        print 'rows.description is %s' % rows[0].description
        assert False

now, if I run this with 'nosetests', here's the error:

E/home/ajohnson/venv/migration/pyramid_helios/pyramid_helios/tests/__init__.py:46:
 
SAWarning: At least one scoped session is already present.  configure() can 
not affect sessions that have already been created.
  DBSession.configure(bind=engine)
.
======================================================================
ERROR: test_payment_cc_setup_approved 
(pyramid_helios.tests.views.test_payments.TestPaymentsUnit)
----------------------------------------------------------------------
Traceback (most recent call last):
  File 
"/home/ajohnson/venv/migration/pyramid_helios/pyramid_helios/tests/views/test_payments.py",
 
line 198, in tearDown
    self.data.teardown()
  File 
"/home/ajohnson/venv/lib/python2.7/site-packages/fixture-1.4-py2.7.egg/fixture/base.py",
 
line 75, in teardown
    self.loader.unload()
  File 
"/home/ajohnson/venv/lib/python2.7/site-packages/fixture-1.4-py2.7.egg/fixture/loadable/loadable.py",
 
line 299, in unload
    self.wrap_in_transaction(unloader, unloading=True)
  File 
"/home/ajohnson/venv/lib/python2.7/site-packages/fixture-1.4-py2.7.egg/fixture/loadable/loadable.py",
 
line 315, in wrap_in_transaction
    self.commit()
  File 
"/home/ajohnson/venv/lib/python2.7/site-packages/fixture-1.4-py2.7.egg/fixture/loadable/sqlalchemy_loadable.py",
 
line 149, in commit
    self.session.flush()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/session.py", line 1587, 
in flush
    self._flush(objects)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/session.py", line 1658, 
in _flush
    flush_context.execute()
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/unitofwork.py", line 
331, in execute
    rec.execute(self)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/unitofwork.py", line 
475, in execute
    uow
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/persistence.py", line 
59, in save_obj
    mapper, table, update)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/orm/persistence.py", line 
485, in _emit_update_statements
    execute(statement, params)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 1450, 
in execute
    params)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 1583, 
in _execute_clauseelement
    compiled_sql, distilled_params
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 1697, 
in _execute_context
    context)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/base.py", line 1690, 
in _execute_context
    context)
  File "build/bdist.linux-x86_64/egg/sqlalchemy/engine/default.py", line 
331, in do_execute
    cursor.execute(statement, parameters)
IntegrityError: (IntegrityError) borr_saved_accts.refnum may not be NULL 
u'UPDATE borr_saved_accts SET refnum=? WHERE borr_saved_accts.rrno = ?' 
(None, u'100198989797')
-------------------- >> begin captured stdout << ---------------------
rows.description is VISA ending in xxxxxxxxxxxx4444

--------------------- >> end captured stdout << ----------------------
-------------------- >> begin captured logging << --------------------
pyramid_helios.views.payments: DEBUG: refn from session is 2234123412
txn.140291144705792: DEBUG: new transaction
--------------------- >> end captured logging << ---------------------

----------------------------------------------------------------------
Ran 2 tests in 0.419s

FAILED (errors=1)



The IntegrityError thing is what I just don't get; it happens on tearDown, 
andI don't know why.

more broadly, how do I allow unitTest and view code DBSession/Transaction 
to play nice with each other when the view code needs to do destructive 
(and creative) ops to the data?

Any help would be most appreciated!

Best,
AKJ

-- 
You received this message because you are subscribed to the Google Groups 
"pylons-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to pylons-discuss+unsubscr...@googlegroups.com.
To post to this group, send email to pylons-discuss@googlegroups.com.
Visit this group at http://groups.google.com/group/pylons-discuss?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to