Skip to content

Commit

Permalink
Add Global Component Contact client plugin.
Browse files Browse the repository at this point in the history
Including:
  - list
  - info
  - create
  - delete
  - delete-match

Add 'DELETE' support in `test_helpers.py`;
Tests added.

JIRA: PDC-1165
  • Loading branch information
xychu committed Nov 11, 2015
1 parent c628647 commit 3c9c189
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 5 deletions.
132 changes: 132 additions & 0 deletions pdc_client/plugins/contact.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015 Red Hat
# Licensed under The MIT License (MIT)
# http://opensource.org/licenses/MIT
#
import json
import sys

from pdc_client import get_paged
from pdc_client.plugin_helpers import (PDCClientPlugin,
extract_arguments,
add_create_update_args)


class GlobalComponentContactPlugin(PDCClientPlugin):
def register(self):
self.set_command('global-component-contact')

list_parser = self.add_action('list', help='list all global component contacts')
self.add_query_arguments(list_parser)
list_parser.set_defaults(func=self.list_global_component_contacts)

info_parser = self.add_action('info', help='display details of a global component contact')
info_parser.add_argument('global_component_contact_id', metavar='GLOBAL_COMPONENT_CONTACT_ID')
info_parser.set_defaults(func=self.info_global_component_contact)

create_parser = self.add_action('create', help='create new global component contact')
self.add_create_arguments(create_parser, required=True)
create_parser.set_defaults(func=self.create_global_component_contact)

delete_parser = self.add_action('delete', help='delete a global component contact')
delete_parser.add_argument('global_component_contact_id', metavar='GLOBAL_COMPONENT_CONTACT_ID')
delete_parser.set_defaults(func=self.delete_global_component_contact)

query_delete_parser = self.add_action('delete-match', help='delete unique matched global component contact')
self.add_query_arguments(query_delete_parser)
query_delete_parser.set_defaults(func=self.delete_matched_global_component_contact)

def add_query_arguments(self, parser, required=False):
filters = ('component contact role email'.split())
for arg in filters:
parser.add_argument('--' + arg.replace('_', '-'), dest='filter_' + arg)

def add_create_arguments(self, parser, required=True):
add_create_update_args(parser,
{'component': {},
'role': {},
'contact__email': {'arg': 'email'}},
{'contact__username': {'arg': 'username'},
'contact__mail_name': {'arg': 'mail-name'},
},
required)

def list_global_component_contacts(self, args):
filters = extract_arguments(args, prefix='filter_')
if not filters:
sys.stderr.write('At least some filter must be used.\n')
sys.exit(1)

This comment has been minimized.

Copy link
@lubomir

lubomir Nov 11, 2015

Member

If this code was replace by list_parser.error('At least …'), it would also print usage information along with the error message. Would that be helpful for the user?

global_component_contacts = get_paged(self.client['global-component-contacts']._, **filters)

if args.json:
print json.dumps(list(global_component_contacts))
return
if global_component_contacts:
self.print_component_contacts(global_component_contacts)

def print_component_contacts(self, global_component_contacts):
fmt = '{:<10} {:25} {:10} {:25} {}'
print fmt.format('ID', 'Component', 'Role', 'Email', 'Name')
for global_component_contact in global_component_contacts:
print fmt.format(
global_component_contact['id'],
global_component_contact['component'],
global_component_contact['role'],
global_component_contact['contact']['email'],
'username' in global_component_contact['contact'] and
global_component_contact['contact']['username'] or
global_component_contact['contact']['mail_name']
)

def info_global_component_contact(self, args, global_component_contact_id=None):
global_component_contact_id = global_component_contact_id or args.global_component_contact_id
global_component_contact = self.client['global-component-contacts'][global_component_contact_id]._()

if args.json:
print json.dumps(global_component_contact)
return

fmt = '{:20} {}'
print fmt.format('ID:', global_component_contact['id'])
print fmt.format('Component:', global_component_contact['component'])
print fmt.format('Role:', global_component_contact['role'])
print 'Contact:'
for name in ('username', 'mail_name'):
if name in global_component_contact['contact']:
print ''.join(['\tName:\t', global_component_contact['contact'][name]])
print ''.join(['\tEmail:\t', global_component_contact['contact']['email']])

def create_global_component_contact(self, args):
data = extract_arguments(args)
self.logger.debug('Creating global component contact with data %r', data)
response = self.client['global-component-contacts']._(data)
self.info_global_component_contact(args, response['id'])

def delete_global_component_contact(self, args):
data = extract_arguments(args)
self.logger.debug('Deleting global component contact: %s',
args.global_component_contact_id)
self.client['global-component-contacts'
][args.global_component_contact_id]._("DELETE", data)

def delete_matched_global_component_contact(self, args):
filters = extract_arguments(args, prefix='filter_')
if not filters:
sys.stderr.write('At least some filter must be used.\n')
sys.exit(1)
global_component_contacts = get_paged(self.client['global-component-contacts']._, **filters)

global_component_contacts = list(global_component_contacts)
match_count = len(global_component_contacts)
if match_count == 1:
self.client['global-component-contacts'
][global_component_contacts[0]['id']]._("DELETE", {})
elif match_count < 1:
print "No match, nothing to do."
else:
print "Multi matches, please delete via ID or provide more restrictions."
self.print_component_contacts(global_component_contacts)


PLUGIN_CLASSES = [GlobalComponentContactPlugin]
18 changes: 13 additions & 5 deletions pdc_client/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ class MockAPI(object):
a dictionary mapping resource URLs to lists of request details. Each
request details is a tuple depending on actual method.
(GET, {filters})
(POST, {request data})
(PATCH, {request data})
(GET, {filters})
(POST, {request data})
(PATCH, {request data})
(DELETE, {request data})
"""
def __init__(self):
self.endpoints = {}
Expand Down Expand Up @@ -79,8 +80,11 @@ def __getitem__(self, key):
return PathAccumulator(key, self)

def __call__(self, *args, **kwargs):
if len(args) == 2 and args[0] == 'PATCH':
return self._handle_patch(args[1])
if len(args) == 2:
if args[0] == 'PATCH':
return self._handle_patch(args[1])
elif args[0] == 'DELETE':
return self._handle_delete(args[1])
if len(args) == 1:
return self._handle_post(args[0])
elif len(args) == 0:
Expand All @@ -90,6 +94,10 @@ def _handle_post(self, data):
self.calls.setdefault(self.will_call, []).append(('POST', data))
return self.endpoints[self.will_call]['POST']

def _handle_delete(self, data):
self.calls.setdefault(self.will_call, []).append(('DELETE', data))
return self.endpoints[self.will_call]['DELETE']

def _handle_get(self, filters):
data = self.endpoints[self.will_call]['GET']
self.calls.setdefault(self.will_call, []).append(('GET', filters))
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"role": "pm", "contact": {"username": "Test Contact", "email": "test_contact@example.com"}, "component": "Test Global Component", "id": "1"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ID: 1
Component: Test Global Component
Role: pm
Contact:
Name: Test Contact
Email: test_contact@example.com
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"role": "pm", "contact": {"username": "Test Contact", "email": "test_contact@example.com"}, "component": "Test Global Component", "id": "1"}]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
ID Component Role Email Name
1 Test Global Component 1 watcher test_contact@example.com Test Contact
2 Test Global Component 2 watcher test_contact@example.com Test Contact
3 Test Global Component 3 watcher test_contact@example.com Test Contact
4 Test Global Component 4 watcher test_contact@example.com Test Contact
5 Test Global Component 5 watcher test_contact@example.com Test Contact
6 Test Global Component 6 watcher test_contact@example.com Test Contact
7 Test Global Component 7 watcher test_contact@example.com Test Contact
8 Test Global Component 8 watcher test_contact@example.com Test Contact
9 Test Global Component 9 watcher test_contact@example.com Test Contact
10 Test Global Component 10 watcher test_contact@example.com Test Contact
11 Test Global Component 11 watcher test_contact@example.com Test Contact
12 Test Global Component 12 watcher test_contact@example.com Test Contact
13 Test Global Component 13 watcher test_contact@example.com Test Contact
14 Test Global Component 14 watcher test_contact@example.com Test Contact
15 Test Global Component 15 watcher test_contact@example.com Test Contact
16 Test Global Component 16 watcher test_contact@example.com Test Contact
17 Test Global Component 17 watcher test_contact@example.com Test Contact
18 Test Global Component 18 watcher test_contact@example.com Test Contact
19 Test Global Component 19 watcher test_contact@example.com Test Contact
20 Test Global Component 20 watcher test_contact@example.com Test Contact
21 Test Global Component 21 watcher test_contact@example.com Test Contact
22 Test Global Component 22 watcher test_contact@example.com Test Contact
23 Test Global Component 23 watcher test_contact@example.com Test Contact
24 Test Global Component 24 watcher test_contact@example.com Test Contact
25 Test Global Component 25 watcher test_contact@example.com Test Contact
105 changes: 105 additions & 0 deletions pdc_client/tests/contact/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2015 Red Hat
# Licensed under The MIT License (MIT)
# http://opensource.org/licenses/MIT
#
from pdc_client.test_helpers import CLITestCase
from pdc_client.runner import Runner


class GlobalComponentContactTestCase(CLITestCase):
def setUp(self):
self.runner = Runner()
self.runner.setup()
self.detail = {
'id': '1',
'component': 'Test Global Component',
'role': 'pm',
'contact': {
'username': 'Test Contact',
'email': 'test_contact@example.com',
}
}

def _setup_detail(self, api):
api.add_endpoint('global-component-contacts/1', 'GET', self.detail)

def test_list_without_filters(self, api):
with self.expect_failure():
self.runner.run(['global-component-contact', 'list'])

def test_list_multi_page(self, api):
api.add_endpoint('global-component-contacts', 'GET', [
{'id': x,
'component': 'Test Global Component %s' % x,
'role': 'watcher',
'contact': {
'username': 'Test Contact',
'email': 'test_contact@example.com',
}}
for x in range(1, 26)
])
with self.expect_output('global_component_contact/list_multi_page.txt'):
self.runner.run(['global-component-contact', 'list',
'--role', 'watcher'])
self.assertEqual(api.calls['global-component-contacts'],
[('GET', {'page': 1, 'role': 'watcher'}),
('GET', {'page': 2, 'role': 'watcher'})])

def test_detail(self, api):
self._setup_detail(api)
with self.expect_output('global_component_contact/detail.txt'):
self.runner.run(['global-component-contact', 'info', '1'])
self.assertDictEqual(api.calls,
{'global-component-contacts/1': [('GET', {})]})

def test_delete(self, api):
api.add_endpoint('global-component-contacts/1', 'DELETE', None)
with self.expect_output('global_component_contact/empty.txt'):
self.runner.run(['global-component-contact', 'delete', '1'])
self.assertDictEqual(api.calls,
{'global-component-contacts/1': [('DELETE', {})]})

def test_delete_matched(self, api):
api.add_endpoint('global-component-contacts', 'GET', [self.detail])
api.add_endpoint('global-component-contacts/1', 'DELETE', None)
with self.expect_output('global_component_contact/empty.txt'):
self.runner.run(['global-component-contact', 'delete-match', '--role', 'pm'])
self.assertDictEqual(api.calls,
{'global-component-contacts': [('GET', {'page': 1,
'role': 'pm'})],
'global-component-contacts/1': [('DELETE', {})]})

This comment has been minimized.

Copy link
@lubomir

lubomir Nov 11, 2015

Member

It would be nice to also have a test for situation when multiple contacts are matched by the filters.


def test_create(self, api):
api.add_endpoint('global-component-contacts', 'POST', self.detail)
self._setup_detail(api)
with self.expect_output('global_component_contact/detail.txt'):
self.runner.run(['global-component-contact', 'create',
'--component', 'Test Global Component',
'--role', 'pm',
'--username', 'Test Contact',
'--email', 'test_contact@example.com'])
self.assertDictEqual(api.calls,
{'global-component-contacts':
[('POST',
{'component': 'Test Global Component',
'role': 'pm',
'contact': {
'username': 'Test Contact',
'email': 'test_contact@example.com'
}})],
'global-component-contacts/1': [('GET', {})]})

def test_info_json(self, api):
self._setup_detail(api)
with self.expect_output('global_component_contact/detail.json', parse_json=True):
self.runner.run(['--json', 'global-component-contact', 'info', '1'])
self.assertDictEqual(api.calls,
{'global-component-contacts/1': [('GET', {})]})

def test_list_json(self, api):
api.add_endpoint('global-component-contacts', 'GET', [self.detail])
with self.expect_output('global_component_contact/list.json', parse_json=True):
self.runner.run(['--json', 'global-component-contact', 'list',
'--role', 'pm'])

0 comments on commit 3c9c189

Please sign in to comment.