Repository: Nitrate/Nitrate

Issue:
Replace deprecated inspect.getargspec call in log_call
`inspect.getargspec` has been deprecated. Replace it with equivalent function call.


Discussion:

Resolving patch:
diff --git a/src/tcms/xmlrpc/decorators.py b/src/tcms/xmlrpc/decorators.py
--- a/src/tcms/xmlrpc/decorators.py
+++ b/src/tcms/xmlrpc/decorators.py
@@ -28,7 +28,7 @@ def create_log(user, method, args):
 def log_call(*args, **kwargs):
     """Log XMLRPC-specific invocations
 
-    This is copied from kobo.django.xmlrpc.decorators to add custom abitlities,
+    This is copied from kobo.django.xmlrpc.decorators to add custom abilities,
     so that we don't have to wait upstream to make the changes.
 
     Usage::
@@ -43,7 +43,7 @@ def func(request):
         namespace = namespace + '.'
 
     def decorator(function):
-        argspec = inspect.getargspec(function)
+        argspec = inspect.getfullargspec(function)
         # Each XMLRPC method has an HttpRequest argument as the first one,
         # it'll be ignored in the log.
         arg_names = argspec.args[1:]
@@ -51,17 +51,19 @@ def decorator(function):
         @wraps(function)
         def _new_function(request, *args, **kwargs):
             try:
-                known_args = zip(arg_names, args)
+                known_args = list(zip(arg_names, args))
                 unknown_args = list(enumerate(args[len(arg_names):]))
-                keyword_args = [(key, value) for key, value in
-                                kwargs.items()
-                                if (key, value) not in known_args]
+                keyword_args = [
+                    (key, value) for key, value in kwargs.items()
+                    if (key, value) not in known_args
+                ]
 
                 create_log(user=request.user,
                            method=f'{namespace}{function.__name__}',
                            args=str(known_args + unknown_args + keyword_args))
             except Exception:
-                pass
+                logger.exception(
+                    f'Fail to log XMLRPC call on {function.__name__}')
             return function(request, *args, **kwargs)
 
         return _new_function


Test patch:
diff --git a/src/tests/xmlrpc/test_decorators.py b/src/tests/xmlrpc/test_decorators.py
new file mode 100644
--- /dev/null
+++ b/src/tests/xmlrpc/test_decorators.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+
+from unittest import TestCase
+from unittest.mock import Mock, patch
+
+from tcms.xmlrpc.decorators import log_call
+
+
+@log_call(namespace='test')
+def func(request, name, status, plans=None, tags=None):
+    pass
+
+
+@log_call(namespace='test')
+def search(request, values=None):
+    pass
+
+
+@log_call()
+def find_something(request, object_id):
+    pass
+
+
+class TestLogCall(TestCase):
+    """Test log_call"""
+
+    @patch('tcms.xmlrpc.decorators.create_log')
+    def test_call_with_only_args(self, create_log):
+        request = Mock()
+        func(request, 'name1', 'idle')
+
+        create_log.assert_called_once_with(
+            user=request.user,
+            method='test.func',
+            args=str([('name', 'name1'), ('status', 'idle')])
+        )
+
+    @patch('tcms.xmlrpc.decorators.create_log')
+    def test_call_with_args_and_kwargs(self, create_log):
+        request = Mock()
+        func(request, 'name1', 'idle', tags=['tag1', 'tag2'])
+
+        create_log.assert_called_once_with(
+            user=request.user,
+            method='test.func',
+            args=str([
+                ('name', 'name1'),
+                ('status', 'idle'),
+                ('tags', ['tag1', 'tag2'])
+            ])
+        )
+
+    @patch('tcms.xmlrpc.decorators.create_log')
+    def test_call_with_only_kwargs(self, create_log):
+        request = Mock()
+        search(request, values={'name': 'name1', 'status': 'running'})
+
+        create_log.assert_called_once_with(
+            user=request.user,
+            method='test.search',
+            args=str([('values', {'name': 'name1', 'status': 'running'})])
+        )
+
+    @patch('tcms.xmlrpc.decorators.create_log')
+    def test_log_without_namespace(self, create_log):
+        request = Mock()
+        find_something(request, 100)
+
+        create_log.assert_called_once_with(
+            user=request.user,
+            method='find_something',
+            args=str([('object_id', 100)])
+        )