11from io import BytesIO
2- from urllib .request import HTTPError
2+ from urllib .request import HTTPError , urljoin
33from unittest import mock , TestCase
4- from unittest .mock import call , sentinel
4+ from unittest .mock import ANY , call , sentinel
55
66import odooly
77
8- sample_context = {'lang' : 'en_US' , 'tz' : 'Europe/Zurich' }
98type_call = type (call )
109
1110
@@ -20,65 +19,42 @@ def popvalue(self):
2019
2120def OBJ (model , method , * params , ** kw ):
2221 if 'context' not in kw :
23- kw ['context' ] = sample_context
22+ kw ['context' ] = { ** OdooTestCase . user_context }
2423 elif kw ['context' ] is None :
2524 del kw ['context' ]
2625 return ('object.execute_kw' , sentinel .AUTH , model , method , params ) + ((kw ,) if kw else ())
2726
2827
29- class XmlRpcTestCase (TestCase ):
28+ class OdooTestCase (TestCase ):
3029 server_version = None
3130 server = "http://192.0.2.199:9999"
3231 database = user = password = uid = None
32+ user_context = {'lang' : 'en_US' , 'tz' : 'Europe/Zurich' }
3333 maxDiff = None
3434
3535 def setUp (self ):
3636 self .addCleanup (mock .patch .stopall )
3737 self .stdout = mock .patch ('sys.stdout' , new = PseudoFile ()).start ()
3838 self .stderr = mock .patch ('sys.stderr' , new = PseudoFile ()).start ()
39+ self .http_request = self ._patch_http_request ()
3940
4041 # Clear the login cache
4142 mock .patch .dict ('odooly.Env._cache' , clear = True ).start ()
4243
4344 # Avoid hanging on getpass
4445 mock .patch ('odooly.getpass' , side_effect = RuntimeError ).start ()
4546
46- self .service = self ._patch_service ()
47- if self .server and self .database :
48- # create the client
49- self .client = odooly .Client (
50- self .server , self .database , self .user , self .password )
51- self .env = self .client .env
52- # reset the mock
53- self .service .reset_mock ()
54-
55- def _patch_http_request (self , uid = None , context = sample_context ):
47+ def _patch_http_request (self , uid = None , context = None ):
5648 def func (url , * , method = 'POST' , data = None , json = None , headers = None ):
5749 if url .endswith ("/web/session/authenticate" ):
58- result = {'uid' : uid or self .uid , 'user_context' : context }
50+ result = {'uid' : uid or self .uid , 'user_context' : context or self . user_context }
5951 else :
6052 with HTTPError (url , 404 , 'Not Found' , headers , BytesIO ()) as not_found :
6153 raise not_found
6254 return {'result' : result }
6355 return mock .patch ('odooly.HTTPSession.request' , side_effect = func ).start ()
6456
65- def _patch_service (self ):
66- def get_svc (server , name , * args , ** kwargs ):
67- return getattr (svcs , name )
68- patcher = mock .patch ('odooly.Service' , side_effect = get_svc )
69- svcs = patcher .start ()
70- svcs .stop = patcher .stop
71- for svc_name in 'db common object wizard report' .split ():
72- svcs .attach_mock (mock .Mock (name = svc_name ), svc_name )
73- # Default values
74- svcs .db .server_version .return_value = self .server_version
75- svcs .db .list .return_value = [self .database ]
76- svcs .common .login .return_value = self .uid
77- # env['res.users'].context_get()
78- svcs .object .execute_kw .return_value = sample_context
79- return svcs
80-
81- def _assertCalls (self , mock_ , expected_calls ):
57+ def assertMockCalls (self , mock_ , expected_calls ):
8258 for idx , expected in enumerate (expected_calls ):
8359 if isinstance (expected , str ):
8460 if expected [:4 ] == 'call' :
@@ -88,17 +64,22 @@ def _assertCalls(self, mock_, expected_calls):
8864 self .assertSequenceEqual (mock_ .mock_calls , expected_calls )
8965 mock_ .reset_mock ()
9066
91- def assertServiceCalls (self , * expected_args ):
67+ def assertRequests (self , * expected_args ):
68+ server = urljoin (self .server , '/' ).rstrip ('/' )
9269 expected_calls = list (expected_args )
9370 for idx , expected in enumerate (expected_calls ):
94- if not isinstance (expected , type_call ) and isinstance (expected , tuple ):
95- rpcmethod = expected [0 ]
96- if len (expected ) > 1 and expected [1 ] == sentinel .AUTH :
97- args = (self .database , self .uid , self .password ) + expected [2 :]
98- else :
99- args = expected [1 :]
100- expected_calls [idx ] = getattr (call , rpcmethod )(* args )
101- self ._assertCalls (self .service , expected_calls )
71+ if isinstance (expected , tuple ):
72+ if expected [0 ].startswith ('/json/2/' ):
73+ headers = {
74+ 'Authorization' : f'Bearer { self .password } ' ,
75+ 'Content-Type' : 'application/json' ,
76+ 'X-Odoo-Database' : self .database ,
77+ }
78+ expected_calls [idx ] = call (f"{ server } { expected [0 ]} " , json = expected [1 ], headers = headers )
79+ elif expected [0 ].startswith ('/web/' ):
80+ jsonrpc_params = {'jsonrpc' : '2.0' , 'method' : 'call' , 'params' : expected [1 ], 'id' : ANY }
81+ expected_calls [idx ] = call (f"{ server } { expected [0 ]} " , json = jsonrpc_params )
82+ self .assertMockCalls (self .http_request , expected_calls )
10283
10384 def assertOutput (self , stdout = '' , stderr = '' , startswith = False ):
10485 # compare with ANY to make sure output is not empty
@@ -117,5 +98,47 @@ def assertOutput(self, stdout='', stderr='', startswith=False):
11798 stdout_value = stdout_value [:len (stdout )]
11899 self .assertMultiLineEqual (stdout_value , stdout )
119100
101+
102+ class XmlRpcTestCase (OdooTestCase ):
103+
104+ def setUp (self ):
105+ super ().setUp ()
106+ self .service = self ._patch_service ()
107+ if self .server and self .database :
108+ # create the client
109+ self .client = odooly .Client (
110+ self .server , self .database , self .user , self .password )
111+ self .env = self .client .env
112+ # reset the mock
113+ self .service .reset_mock ()
114+
115+ def _patch_service (self ):
116+ def get_svc (server , name , * args , ** kwargs ):
117+ return getattr (svcs , name )
118+ patcher = mock .patch ('odooly.Service' , side_effect = get_svc )
119+ svcs = patcher .start ()
120+ svcs .stop = patcher .stop
121+ for svc_name in 'db common object wizard report' .split ():
122+ svcs .attach_mock (mock .Mock (name = svc_name ), svc_name )
123+ # Default values
124+ svcs .db .server_version .return_value = self .server_version
125+ svcs .db .list .return_value = [self .database ]
126+ svcs .common .login .return_value = self .uid
127+ # env['res.users'].context_get()
128+ svcs .object .execute_kw .return_value = self .user_context
129+ return svcs
130+
131+ def assertServiceCalls (self , * expected_args ):
132+ expected_calls = list (expected_args )
133+ for idx , expected in enumerate (expected_calls ):
134+ if not isinstance (expected , type_call ) and isinstance (expected , tuple ):
135+ rpcmethod = expected [0 ]
136+ if len (expected ) > 1 and expected [1 ] == sentinel .AUTH :
137+ args = (self .database , self .uid , self .password ) + expected [2 :]
138+ else :
139+ args = expected [1 :]
140+ expected_calls [idx ] = getattr (call , rpcmethod )(* args )
141+ self .assertMockCalls (self .service , expected_calls )
142+
120143 # Legacy
121144 assertCalls = assertServiceCalls
0 commit comments