38 from logilab.common.decorators import cached, classproperty, clear_cache, iclassmethod |
38 from logilab.common.decorators import cached, classproperty, clear_cache, iclassmethod |
39 from logilab.common.deprecation import deprecated, class_deprecated |
39 from logilab.common.deprecation import deprecated, class_deprecated |
40 from logilab.common.shellutils import getlogin |
40 from logilab.common.shellutils import getlogin |
41 |
41 |
42 from cubicweb import ValidationError, NoSelectableObject, AuthenticationError |
42 from cubicweb import ValidationError, NoSelectableObject, AuthenticationError |
43 from cubicweb import cwconfig, dbapi, devtools, web, server |
43 from cubicweb import cwconfig, dbapi, devtools, web, server, repoapi |
44 from cubicweb.utils import json |
44 from cubicweb.utils import json |
45 from cubicweb.sobjects import notification |
45 from cubicweb.sobjects import notification |
46 from cubicweb.web import Redirect, application |
46 from cubicweb.web import Redirect, application |
47 from cubicweb.server.hook import SendMailOp |
47 from cubicweb.server.hook import SendMailOp |
48 from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS |
48 from cubicweb.devtools import SYSTEM_ENTITIES, SYSTEM_RELATIONS, VIEW_VALIDATORS |
144 |
144 |
145 def __getattr__(self, attrname): |
145 def __getattr__(self, attrname): |
146 return getattr(self.cnx, attrname) |
146 return getattr(self.cnx, attrname) |
147 |
147 |
148 def __enter__(self): |
148 def __enter__(self): |
149 return self.cnx.__enter__() |
149 # already open |
|
150 return self.cnx |
150 |
151 |
151 def __exit__(self, exctype, exc, tb): |
152 def __exit__(self, exctype, exc, tb): |
152 try: |
153 try: |
153 return self.cnx.__exit__(exctype, exc, tb) |
154 return self.cnx.__exit__(exctype, exc, tb) |
154 finally: |
155 finally: |
155 self.cnx.close() |
|
156 self.testcase.restore_connection() |
156 self.testcase.restore_connection() |
157 |
157 |
158 # base class for cubicweb tests requiring a full cw environments ############### |
158 # base class for cubicweb tests requiring a full cw environments ############### |
159 |
159 |
160 class CubicWebTC(TestCase): |
160 class CubicWebTC(TestCase): |
179 test_db_id = DEFAULT_EMPTY_DB_ID |
179 test_db_id = DEFAULT_EMPTY_DB_ID |
180 _cnxs = set() # establised connection |
180 _cnxs = set() # establised connection |
181 # stay on connection for leak detection purpose |
181 # stay on connection for leak detection purpose |
182 |
182 |
183 def __init__(self, *args, **kwargs): |
183 def __init__(self, *args, **kwargs): |
184 self._cnx = None # current connection |
184 self._admin_session = None |
|
185 self._admin_clt_cnx = None |
|
186 self._current_session = None |
|
187 self._current_clt_cnx = None |
185 self.repo = None |
188 self.repo = None |
186 self.websession = None |
|
187 super(CubicWebTC, self).__init__(*args, **kwargs) |
189 super(CubicWebTC, self).__init__(*args, **kwargs) |
188 |
190 |
189 # repository connection handling ########################################### |
191 # repository connection handling ########################################### |
190 |
192 |
191 # Too much complicated stuff. the class doesn't need to bear the repo anymore |
|
192 def set_cnx(self, cnx): |
193 def set_cnx(self, cnx): |
193 self._cnxs.add(cnx) |
194 # XXX we want to deprecate this |
194 self._cnx = cnx |
195 assert getattr(cnx, '_session', None) is not None |
|
196 if cnx is self._admin_clt_cnx: |
|
197 self._pop_custom_cnx() |
|
198 else: |
|
199 self._cnxs.add(cnx) # register the cns to make sure it is removed |
|
200 self._current_session = cnx._session |
|
201 self._current_clt_cnx = cnx |
195 |
202 |
196 @property |
203 @property |
197 def cnx(self): |
204 def cnx(self): |
198 return self._cnx |
205 # XXX we want to deprecate this |
|
206 clt_cnx = self._current_clt_cnx |
|
207 if clt_cnx is None: |
|
208 clt_cnx = self._admin_clt_cnx |
|
209 return clt_cnx |
199 |
210 |
200 def _close_cnx(self): |
211 def _close_cnx(self): |
|
212 """ensure that all cnx used by a test have been closed""" |
201 for cnx in list(self._cnxs): |
213 for cnx in list(self._cnxs): |
202 if not cnx._closed: |
214 if cnx._open and not cnx._session.closed: |
203 cnx.rollback() |
215 cnx.rollback() |
204 cnx.close() |
216 cnx.close() |
205 self._cnxs.remove(cnx) |
217 self._cnxs.remove(cnx) |
206 |
218 |
207 @property |
219 @property |
208 def session(self): |
220 def session(self): |
209 """return current server side session (using default manager account)""" |
221 """return current server side session""" |
210 session = self.repo._sessions[self.cnx.sessionid] |
222 # XXX We want to use a srv_connection instead and deprecate this |
|
223 # property |
|
224 session = self._current_session |
|
225 if session is None: |
|
226 session = self._admin_session |
|
227 session.set_cnx(self._admin_clt_cnx._cnxid) |
211 session.set_cnxset() |
228 session.set_cnxset() |
212 return session |
229 return session |
213 |
230 |
|
231 @property |
|
232 def websession(self): |
|
233 return self.session |
|
234 |
|
235 @property |
|
236 def adminsession(self): |
|
237 """return current server side session (using default manager account)""" |
|
238 return self._admin_session |
|
239 |
214 def login(self, login, **kwargs): |
240 def login(self, login, **kwargs): |
215 """return a connection for the given login/password""" |
241 """return a connection for the given login/password""" |
|
242 __ = kwargs.pop('autoclose', True) # not used anymore |
216 if login == self.admlogin: |
243 if login == self.admlogin: |
217 self.restore_connection() |
|
218 # definitly don't want autoclose when used as a context manager |
244 # definitly don't want autoclose when used as a context manager |
219 return self.cnx |
245 clt_cnx = repoapi.ClientConnection(self._admin_session) |
220 autoclose = kwargs.pop('autoclose', True) |
246 else: |
221 if not kwargs: |
247 if not kwargs: |
222 kwargs['password'] = str(login) |
248 kwargs['password'] = str(login) |
223 self.set_cnx(dbapi._repo_connect(self.repo, unicode(login), **kwargs)) |
249 clt_cnx = repoapi.connect(self.repo, login, **kwargs) |
224 self.websession = dbapi.DBAPISession(self.cnx) |
250 self.set_cnx(clt_cnx) |
225 if autoclose: |
251 clt_cnx.__enter__() |
226 return TestCaseConnectionProxy(self, self.cnx) |
252 return TestCaseConnectionProxy(self, clt_cnx) |
227 return self.cnx |
|
228 |
253 |
229 def restore_connection(self): |
254 def restore_connection(self): |
230 if not self.cnx is self._orig_cnx[0]: |
255 self._pop_custom_cnx() |
231 if not self.cnx._closed: |
256 |
232 self.cnx.close() |
257 def _pop_custom_cnx(self): |
233 cnx, self.websession = self._orig_cnx |
258 if self._current_clt_cnx is not None: |
234 self.set_cnx(cnx) |
259 if self._current_clt_cnx._open: |
|
260 self._current_clt_cnx.close() |
|
261 if not self._current_session.closed: |
|
262 self.repo.close(self._current_session.id) |
|
263 self._current_clt_cnx = None |
|
264 self._current_session = None |
235 |
265 |
236 #XXX this doesn't need to a be classmethod anymore |
266 #XXX this doesn't need to a be classmethod anymore |
237 def _init_repo(self): |
267 def _init_repo(self): |
238 """init the repository and connection to it. |
268 """init the repository and connection to it. |
239 """ |
269 """ |
241 self.init_config(self.config) |
271 self.init_config(self.config) |
242 # get or restore and working db. |
272 # get or restore and working db. |
243 db_handler = devtools.get_test_db_handler(self.config) |
273 db_handler = devtools.get_test_db_handler(self.config) |
244 db_handler.build_db_cache(self.test_db_id, self.pre_setup_database) |
274 db_handler.build_db_cache(self.test_db_id, self.pre_setup_database) |
245 |
275 |
246 self.repo, cnx = db_handler.get_repo_and_cnx(self.test_db_id) |
276 db_handler.restore_database(self.test_db_id) |
|
277 self.repo = db_handler.get_repo(startup=True) |
|
278 # get an admin session (without actual login) |
|
279 sources = db_handler.config.sources() |
|
280 login = unicode(sources['admin']['login']) |
|
281 with self.repo.internal_session() as session: |
|
282 rset = session.execute('CWUser U WHERE U login %(u)s', {'u': login}) |
|
283 user = rset.get_entity(0, 0) |
|
284 user.groups |
|
285 user.properties |
|
286 from cubicweb.server.session import Session |
|
287 self._admin_session = Session(user, self.repo) |
|
288 self.repo._sessions[self._admin_session.id] = self._admin_session |
|
289 self._admin_session.user._cw = self._admin_session |
|
290 self._admin_clt_cnx = repoapi.ClientConnection(self._admin_session) |
|
291 |
247 # no direct assignation to cls.cnx anymore. |
292 # no direct assignation to cls.cnx anymore. |
248 # cnx is now an instance property that use a class protected attributes. |
293 # cnx is now an instance property that use a class protected attributes. |
249 self.set_cnx(cnx) |
294 self._cnxs.add(self._admin_clt_cnx) |
250 self.websession = dbapi.DBAPISession(cnx, self.admlogin) |
295 self._admin_clt_cnx.__enter__() |
251 self._orig_cnx = (cnx, self.websession) |
|
252 self.config.repository = lambda x=None: self.repo |
296 self.config.repository = lambda x=None: self.repo |
253 |
297 |
254 # db api ################################################################## |
298 # db api ################################################################## |
255 |
299 |
256 @nocoverage |
300 @nocoverage |
257 def cursor(self, req=None): |
301 def cursor(self, req=None): |
258 return self.cnx.cursor(req or self.request()) |
302 if req is not None: |
|
303 return req.cnx |
|
304 else: |
|
305 return self.cnx |
259 |
306 |
260 @nocoverage |
307 @nocoverage |
261 def execute(self, rql, args=None, eidkey=None, req=None): |
308 def execute(self, rql, args=None, eidkey=None, req=None): |
262 """executes <rql>, builds a resultset, and returns a couple (rset, req) |
309 """executes <rql>, builds a resultset, and returns a couple (rset, req) |
263 where req is a FakeRequest |
310 where req is a FakeRequest |
285 self.session.set_cnxset() # ensure cnxset still set after commit |
332 self.session.set_cnxset() # ensure cnxset still set after commit |
286 |
333 |
287 requestcls = fake.FakeRequest |
334 requestcls = fake.FakeRequest |
288 def request(self, rollbackfirst=False, url=None, headers={}, **kwargs): |
335 def request(self, rollbackfirst=False, url=None, headers={}, **kwargs): |
289 """return a web ui request""" |
336 """return a web ui request""" |
|
337 if rollbackfirst: |
|
338 self.cnx.rollback() |
290 req = self.requestcls(self.vreg, url=url, headers=headers, form=kwargs) |
339 req = self.requestcls(self.vreg, url=url, headers=headers, form=kwargs) |
291 if rollbackfirst: |
340 req.set_cnx(self.cnx) |
292 self.websession.cnx.rollback() |
|
293 req.set_session(self.websession) |
|
294 return req |
341 return req |
295 |
|
296 @property |
|
297 def adminsession(self): |
|
298 """return current server side session (using default manager account)""" |
|
299 return self.repo._sessions[self._orig_cnx[0].sessionid] |
|
300 |
|
301 |
|
302 |
342 |
303 # server side db api ####################################################### |
343 # server side db api ####################################################### |
304 |
344 |
305 def sexecute(self, rql, args=None, eid_key=None): |
345 def sexecute(self, rql, args=None, eid_key=None): |
306 if eid_key is not None: |
346 if eid_key is not None: |
407 self.commit() |
447 self.commit() |
408 MAILBOX[:] = [] # reset mailbox |
448 MAILBOX[:] = [] # reset mailbox |
409 |
449 |
410 def tearDown(self): |
450 def tearDown(self): |
411 # XXX hack until logilab.common.testlib is fixed |
451 # XXX hack until logilab.common.testlib is fixed |
|
452 if self._admin_clt_cnx is not None: |
|
453 if self._admin_clt_cnx._open: |
|
454 self._admin_clt_cnx.close() |
|
455 self._admin_clt_cnx = None |
|
456 if self._admin_session is not None: |
|
457 if not self._admin_session.closed: |
|
458 self.repo.close(self._admin_session.id) |
|
459 self._admin_session = None |
412 while self._cleanups: |
460 while self._cleanups: |
413 cleanup, args, kwargs = self._cleanups.pop(-1) |
461 cleanup, args, kwargs = self._cleanups.pop(-1) |
414 cleanup(*args, **kwargs) |
462 cleanup(*args, **kwargs) |
415 |
463 |
416 def _patch_SendMailOp(self): |
464 def _patch_SendMailOp(self): |