Merge branch 'v1' into no-resizing
continuous-integration/drone/pr Build is passing
Details
continuous-integration/drone/pr Build is passing
Details
This commit is contained in:
commit
65131337d1
|
@ -6,6 +6,7 @@ from .positions import positions
|
|||
from .updateprograms import updateprograms
|
||||
from .mysql import mysql
|
||||
from .postgresql import postgresql
|
||||
from .mailman import mailman
|
||||
|
||||
|
||||
@click.group()
|
||||
|
@ -19,3 +20,4 @@ cli.add_command(positions)
|
|||
cli.add_command(updateprograms)
|
||||
cli.add_command(mysql)
|
||||
cli.add_command(postgresql)
|
||||
cli.add_command(mailman)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import click
|
||||
|
||||
from ..utils import http_post, http_delete
|
||||
from .utils import handle_sync_response
|
||||
|
||||
|
||||
@click.group(short_help='Manage mailing list subscriptions')
|
||||
def mailman():
|
||||
pass
|
||||
|
||||
|
||||
@mailman.command(short_help='Subscribe a member to a mailing list')
|
||||
@click.argument('username')
|
||||
@click.argument('mailing_list')
|
||||
def subscribe(username, mailing_list):
|
||||
click.confirm(f'Are you sure you want to subscribe {username} to {mailing_list}?', abort=True)
|
||||
resp = http_post(f'/api/mailman/{mailing_list}/{username}')
|
||||
handle_sync_response(resp)
|
||||
click.echo('Done.')
|
||||
|
||||
|
||||
@mailman.command(short_help='Unsubscribe a member from a mailing list')
|
||||
@click.argument('username')
|
||||
@click.argument('mailing_list')
|
||||
def unsubscribe(username, mailing_list):
|
||||
click.confirm(f'Are you sure you want to unsubscribe {username} from {mailing_list}?', abort=True)
|
||||
resp = http_delete(f'/api/mailman/{mailing_list}/{username}')
|
||||
handle_sync_response(resp)
|
||||
click.echo('Done.')
|
|
@ -60,10 +60,7 @@ class CeoFrame(Frame):
|
|||
after some kind of operation was completed.
|
||||
This is called from Model.reset().
|
||||
"""
|
||||
# Reset input fields to ''.
|
||||
# This is causing an Exception to be raised...
|
||||
# self.save()
|
||||
# self.data = {key: '' for key in self.data}
|
||||
pass
|
||||
|
||||
def clear_layouts(self):
|
||||
self._layouts.clear()
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
from copy import deepcopy
|
||||
|
||||
from zope import component
|
||||
|
||||
from ceo_common.interfaces import IConfig
|
||||
|
||||
|
||||
class Model:
|
||||
"""A convenient place to store View data persistently."""
|
||||
|
||||
def __init__(self):
|
||||
cfg = component.getUtility(IConfig)
|
||||
|
||||
self.screen = None
|
||||
self.views = []
|
||||
self.title = None
|
||||
|
@ -19,6 +25,8 @@ class Model:
|
|||
'uid': '',
|
||||
},
|
||||
}
|
||||
for pos in cfg.get('positions_available'):
|
||||
self._initial_viewdata[pos] = ''
|
||||
self.viewdata = deepcopy(self._initial_viewdata)
|
||||
# data which is shared between multiple views
|
||||
self.is_club_rep = False
|
||||
|
|
|
@ -31,17 +31,6 @@ class GetPositionsView(CeoFrame):
|
|||
)
|
||||
self._position_widgets = {}
|
||||
|
||||
def _add_blank_line(self):
|
||||
self._main_layout.add_widget(Label(' ', 0))
|
||||
self._main_layout.add_widget(Label(' ', 2))
|
||||
|
||||
def _add_pair(self, key: str, val: str):
|
||||
key_widget = Label(key + ':', align='>')
|
||||
value_widget = Label(val, align='<')
|
||||
self._main_layout.add_widget(key_widget, 0)
|
||||
self._main_layout.add_widget(value_widget, 2)
|
||||
return value_widget
|
||||
|
||||
def _on_load(self):
|
||||
cfg = component.getUtility(IConfig)
|
||||
avail = cfg.get('positions_available')
|
||||
|
@ -73,6 +62,17 @@ class GetPositionsView(CeoFrame):
|
|||
self.clear_flash_message(force_update=True)
|
||||
Thread(target=target).start()
|
||||
|
||||
def _add_blank_line(self):
|
||||
self._main_layout.add_widget(Label(' ', 0))
|
||||
self._main_layout.add_widget(Label(' ', 2))
|
||||
|
||||
def _add_pair(self, key: str, val: str):
|
||||
key_widget = Label(key + ':', align='>')
|
||||
value_widget = Label(val, align='<')
|
||||
self._main_layout.add_widget(key_widget, 0)
|
||||
self._main_layout.add_widget(value_widget, 2)
|
||||
return value_widget
|
||||
|
||||
def _ceoframe_on_reset(self):
|
||||
super()._ceoframe_on_reset()
|
||||
# clear the labels
|
||||
|
|
|
@ -16,6 +16,8 @@ def http_request(method: str, path: str, **kwargs) -> requests.Response:
|
|||
cfg = component.getUtility(IConfig)
|
||||
if path.startswith('/api/db'):
|
||||
host = cfg.get('ceod_database_host')
|
||||
elif path.startswith('/api/mailman'):
|
||||
host = cfg.get('ceod_mailman_host')
|
||||
else:
|
||||
host = cfg.get('ceod_admin_host')
|
||||
return client.request(
|
||||
|
|
|
@ -67,7 +67,7 @@ def authz_restrict_to_groups(f: Callable, allowed_groups: List[str]) -> Callable
|
|||
def authz_restrict_to_staff(f: Callable) -> Callable:
|
||||
"""A decorator to restrict an endpoint to staff members."""
|
||||
|
||||
allowed_groups = ['office', 'staff', 'adm']
|
||||
allowed_groups = ['syscom', 'exec', 'office', 'staff', 'adm']
|
||||
return authz_restrict_to_groups(f, allowed_groups)
|
||||
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ class MailService:
|
|||
if '@' in auth_user:
|
||||
auth_user = auth_user[:auth_user.index('@')]
|
||||
|
||||
if user.is_club():
|
||||
if user.terms:
|
||||
prog = 'addclubrep'
|
||||
desc = 'Club Rep'
|
||||
else:
|
||||
|
|
|
@ -23,7 +23,7 @@ class MockHandler:
|
|||
async def handle_DATA(self, server, session, envelope):
|
||||
msg = {
|
||||
'from': envelope.mail_from,
|
||||
'to': envelope.rcpt_tos[0],
|
||||
'to': ','.join(envelope.rcpt_tos),
|
||||
'content': envelope.content.decode(),
|
||||
}
|
||||
self.mock_server.messages.append(msg)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
from click.testing import CliRunner
|
||||
|
||||
from ceo.cli import cli
|
||||
|
||||
|
||||
def test_mailman_subscribe(cli_setup, mock_mailman_server):
|
||||
mock_mailman_server.clear()
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ['mailman', 'subscribe', 'test_1', 'exec'], input='y\n')
|
||||
expected = (
|
||||
"Are you sure you want to subscribe test_1 to exec? [y/N]: y\n"
|
||||
"Done.\n"
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == expected
|
||||
|
||||
result = runner.invoke(cli, ['mailman', 'unsubscribe', 'test_1', 'exec'], input='y\n')
|
||||
expected = (
|
||||
"Are you sure you want to unsubscribe test_1 from exec? [y/N]: y\n"
|
||||
"Done.\n"
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == expected
|
|
@ -6,6 +6,7 @@ uw_domain = uwaterloo.internal
|
|||
# this is the host with the ceod/admin Kerberos key
|
||||
admin_host = phosphoric-acid
|
||||
database_host = coffee
|
||||
mailman_host = mail
|
||||
use_https = false
|
||||
port = 9987
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ def test_api_user_not_found(client):
|
|||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def create_user_resp(client, mocks_for_create_user):
|
||||
def create_user_resp(client, mocks_for_create_user, mock_mail_server):
|
||||
mock_mail_server.messages.clear()
|
||||
status, data = client.post('/api/members', json={
|
||||
'uid': 'test_1',
|
||||
'cn': 'Test One',
|
||||
|
@ -35,7 +36,7 @@ def create_user_result(create_user_resp):
|
|||
return data[-1]['result']
|
||||
|
||||
|
||||
def test_api_create_user(cfg, create_user_resp):
|
||||
def test_api_create_user(cfg, create_user_resp, mock_mail_server):
|
||||
_, data = create_user_resp
|
||||
min_uid = cfg.get('members_min_id')
|
||||
expected = [
|
||||
|
@ -62,6 +63,12 @@ def test_api_create_user(cfg, create_user_resp):
|
|||
}},
|
||||
]
|
||||
assert data == expected
|
||||
# Two messages should have been sent: a welcome message to the new member,
|
||||
# and an announcement to the ceo mailing list
|
||||
assert len(mock_mail_server.messages) == 2
|
||||
assert mock_mail_server.messages[0]['to'] == 'test_1@csclub.internal'
|
||||
assert mock_mail_server.messages[1]['to'] == 'ceo@csclub.internal,ctdalek@csclub.internal'
|
||||
mock_mail_server.messages.clear()
|
||||
|
||||
|
||||
def test_api_next_uid(cfg, client, create_user_result):
|
||||
|
|
|
@ -208,7 +208,6 @@ def mock_mailman_server():
|
|||
|
||||
@pytest.fixture(scope='session')
|
||||
def mailman_srv(mock_mailman_server, cfg, http_client):
|
||||
# TODO: test the RemoteMailmanService as well
|
||||
mailman = MailmanService()
|
||||
component.getGlobalSiteManager().registerUtility(mailman, IMailmanService)
|
||||
return mailman
|
||||
|
|
Loading…
Reference in New Issue