Skip to content

Commit 5578702

Browse files
committed
feat: convert user subcommand
1 parent 2d7c1ff commit 5578702

File tree

6 files changed

+289
-71
lines changed

6 files changed

+289
-71
lines changed

bin/convert.sh

Lines changed: 0 additions & 65 deletions
This file was deleted.

compiler_admin/commands/convert.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE
2+
from compiler_admin.services.google import (
3+
GROUP_PARTNERS,
4+
GROUP_STAFF,
5+
OU_CONTRACTORS,
6+
OU_PARTNERS,
7+
OU_STAFF,
8+
add_user_to_group,
9+
move_user_ou,
10+
remove_user_from_group,
11+
user_account_name,
12+
user_exists,
13+
user_is_partner,
14+
user_is_staff,
15+
)
16+
17+
18+
ACCOUNT_TYPE_OU = {"contractor": OU_CONTRACTORS, "partner": OU_PARTNERS, "staff": OU_STAFF}
19+
20+
21+
def convert(username: str, account_type: str) -> int:
22+
f"""Convert a user of one type to another.
23+
Args:
24+
username (str): The account to convert. Must exist already.
25+
26+
account_type (str): One of {", ".join(ACCOUNT_TYPE_OU.keys())}
27+
Returns:
28+
A value indicating if the operation succeeded or failed.
29+
"""
30+
account = user_account_name(username)
31+
32+
if not user_exists(account):
33+
print(f"User does not exist: {account}")
34+
return RESULT_FAILURE
35+
36+
if account_type not in ACCOUNT_TYPE_OU:
37+
print(f"Unknown account type for conversion: {account_type}")
38+
return RESULT_FAILURE
39+
40+
print(f"User exists, converting to: {account_type} for {account}")
41+
res = RESULT_SUCCESS
42+
43+
if account_type == "contractor":
44+
if user_is_partner(account):
45+
res += remove_user_from_group(account, GROUP_PARTNERS)
46+
res += remove_user_from_group(account, GROUP_STAFF)
47+
elif user_is_staff(account):
48+
res = remove_user_from_group(account, GROUP_STAFF)
49+
50+
elif account_type == "staff":
51+
if user_is_partner(account):
52+
res += remove_user_from_group(account, GROUP_PARTNERS)
53+
elif user_is_staff(account):
54+
print(f"User is already staff: {account}")
55+
return RESULT_FAILURE
56+
res += add_user_to_group(account, GROUP_STAFF)
57+
58+
elif account_type == "partner":
59+
if user_is_partner(account):
60+
print(f"User is already partner: {account}")
61+
return RESULT_FAILURE
62+
if not user_is_staff(account):
63+
res += add_user_to_group(account, GROUP_STAFF)
64+
res += add_user_to_group(account, GROUP_PARTNERS)
65+
66+
res += move_user_ou(account, ACCOUNT_TYPE_OU[account_type])
67+
68+
print(f"Account conversion complete for: {account}")
69+
70+
return RESULT_SUCCESS if res == RESULT_SUCCESS else RESULT_FAILURE

compiler_admin/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from compiler_admin import __version__ as version
55
from compiler_admin.commands.create import create
6+
from compiler_admin.commands.convert import ACCOUNT_TYPE_OU, convert
67
from compiler_admin.commands.delete import delete
78
from compiler_admin.commands.info import info
89
from compiler_admin.commands.init import init
@@ -41,6 +42,11 @@ def _subcmd(name, help, add_username_arg=True) -> argparse.ArgumentParser:
4142

4243
_subcmd("create", help="Create a new user in the Compiler domain.")
4344

45+
convert_parser = _subcmd("convert", help="Convert a user account to a new type.")
46+
convert_parser.add_argument(
47+
"account_type", choices=ACCOUNT_TYPE_OU.keys(), help="Target account type for this conversion."
48+
)
49+
4450
_subcmd("delete", help="Delete a user account.")
4551

4652
offboard_parser = _subcmd("offboard", help="Offboard a user account.")
@@ -59,6 +65,8 @@ def _subcmd(name, help, add_username_arg=True) -> argparse.ArgumentParser:
5965
return info()
6066
elif args.command == "create":
6167
return create(args.username, *extra)
68+
elif args.command == "convert":
69+
return convert(args.username, args.account_type)
6270
elif args.command == "delete":
6371
return delete(args.username)
6472
elif args.command == "init":

tests/commands/test_convert.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import pytest
2+
3+
from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS
4+
from compiler_admin.commands.convert import convert, __name__ as MODULE
5+
6+
7+
@pytest.fixture
8+
def mock_google_user_exists(mock_google_user_exists):
9+
return mock_google_user_exists(MODULE)
10+
11+
12+
@pytest.fixture
13+
def mock_google_user_exists_true(mock_google_user_exists):
14+
mock_google_user_exists.return_value = True
15+
return mock_google_user_exists
16+
17+
18+
@pytest.fixture
19+
def mock_google_add_user_to_group(mock_google_add_user_to_group):
20+
return mock_google_add_user_to_group(MODULE)
21+
22+
23+
@pytest.fixture
24+
def mock_google_move_user_ou(mock_google_move_user_ou):
25+
return mock_google_move_user_ou(MODULE)
26+
27+
28+
@pytest.fixture
29+
def mock_google_remove_user_from_group(mock_google_remove_user_from_group):
30+
return mock_google_remove_user_from_group(MODULE)
31+
32+
33+
@pytest.fixture
34+
def mock_google_user_is_partner(mock_google_user_is_partner):
35+
return mock_google_user_is_partner(MODULE)
36+
37+
38+
@pytest.fixture
39+
def mock_google_user_is_partner_true(mock_google_user_is_partner):
40+
mock_google_user_is_partner.return_value = True
41+
return mock_google_user_is_partner
42+
43+
44+
@pytest.fixture
45+
def mock_google_user_is_partner_false(mock_google_user_is_partner):
46+
mock_google_user_is_partner.return_value = False
47+
return mock_google_user_is_partner
48+
49+
50+
@pytest.fixture
51+
def mock_google_user_is_staff(mock_google_user_is_staff):
52+
return mock_google_user_is_staff(MODULE)
53+
54+
55+
@pytest.fixture
56+
def mock_google_user_is_staff_true(mock_google_user_is_staff):
57+
mock_google_user_is_staff.return_value = True
58+
return mock_google_user_is_staff
59+
60+
61+
@pytest.fixture
62+
def mock_google_user_is_staff_false(mock_google_user_is_staff):
63+
mock_google_user_is_staff.return_value = False
64+
return mock_google_user_is_staff
65+
66+
67+
def test_convert_user_does_not_exists(mock_google_user_exists, mock_google_move_user_ou):
68+
mock_google_user_exists.return_value = False
69+
70+
res = convert("username", "account_type")
71+
72+
assert res == RESULT_FAILURE
73+
assert mock_google_move_user_ou.call_count == 0
74+
75+
76+
@pytest.mark.usefixtures("mock_google_user_exists_true")
77+
def test_convert_user_exists_bad_account_type(mock_google_move_user_ou):
78+
res = convert("username", "account_type")
79+
80+
assert res == RESULT_FAILURE
81+
assert mock_google_move_user_ou.call_count == 0
82+
83+
84+
@pytest.mark.usefixtures(
85+
"mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_false"
86+
)
87+
def test_convert_contractor(mock_google_move_user_ou):
88+
res = convert("username", "contractor")
89+
90+
assert res == RESULT_SUCCESS
91+
mock_google_move_user_ou.assert_called_once()
92+
93+
94+
@pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_true", "mock_google_user_is_staff_false")
95+
def test_convert_contractor_user_is_partner(mock_google_remove_user_from_group, mock_google_move_user_ou):
96+
res = convert("username", "contractor")
97+
98+
assert res == RESULT_SUCCESS
99+
assert mock_google_remove_user_from_group.call_count == 2
100+
mock_google_move_user_ou.assert_called_once()
101+
102+
103+
@pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_true")
104+
def test_convert_contractor_user_is_staff(mock_google_remove_user_from_group, mock_google_move_user_ou):
105+
res = convert("username", "contractor")
106+
107+
assert res == RESULT_SUCCESS
108+
mock_google_remove_user_from_group.assert_called_once()
109+
mock_google_move_user_ou.assert_called_once()
110+
111+
112+
@pytest.mark.usefixtures(
113+
"mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_false"
114+
)
115+
def test_convert_staff(mock_google_add_user_to_group, mock_google_move_user_ou):
116+
res = convert("username", "staff")
117+
118+
assert res == RESULT_SUCCESS
119+
mock_google_add_user_to_group.assert_called_once()
120+
mock_google_move_user_ou.assert_called_once()
121+
122+
123+
@pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_true", "mock_google_user_is_staff_false")
124+
def test_convert_staff_user_is_partner(
125+
mock_google_add_user_to_group, mock_google_remove_user_from_group, mock_google_move_user_ou
126+
):
127+
res = convert("username", "staff")
128+
129+
assert res == RESULT_SUCCESS
130+
mock_google_remove_user_from_group.assert_called_once()
131+
mock_google_add_user_to_group.assert_called_once()
132+
mock_google_move_user_ou.assert_called_once()
133+
134+
135+
@pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_true")
136+
def test_convert_staff_user_is_staff(mock_google_add_user_to_group, mock_google_move_user_ou):
137+
res = convert("username", "staff")
138+
139+
assert res == RESULT_FAILURE
140+
assert mock_google_add_user_to_group.call_count == 0
141+
assert mock_google_move_user_ou.call_count == 0
142+
143+
144+
@pytest.mark.usefixtures(
145+
"mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_false"
146+
)
147+
def test_convert_partner(mock_google_add_user_to_group, mock_google_move_user_ou):
148+
res = convert("username", "partner")
149+
150+
assert res == RESULT_SUCCESS
151+
mock_google_add_user_to_group.call_count == 2
152+
mock_google_move_user_ou.assert_called_once()
153+
154+
155+
@pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_true", "mock_google_user_is_staff_false")
156+
def test_convert_partner_user_is_partner(mock_google_add_user_to_group, mock_google_move_user_ou):
157+
res = convert("username", "partner")
158+
159+
assert res == RESULT_FAILURE
160+
assert mock_google_add_user_to_group.call_count == 0
161+
assert mock_google_move_user_ou.call_count == 0
162+
163+
164+
@pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_true")
165+
def test_convert_partner_user_is_staff(mock_google_add_user_to_group, mock_google_move_user_ou):
166+
res = convert("username", "partner")
167+
168+
assert res == RESULT_SUCCESS
169+
mock_google_add_user_to_group.assert_called_once()
170+
mock_google_move_user_ou.assert_called_once()

tests/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ def _mock_commands_create(module, **kwargs):
1111
return _mock_commands_create
1212

1313

14+
@pytest.fixture
15+
def mock_commands_convert(mocker):
16+
"""Fixture returns a function that patches commands.convert in a given module."""
17+
18+
def _mock_commands_convert(module, **kwargs):
19+
return mocker.patch(f"{module}.convert", **kwargs)
20+
21+
return _mock_commands_convert
22+
23+
1424
@pytest.fixture
1525
def mock_commands_delete(mocker):
1626
"""Fixture returns a function that patches commands.delete in a given module."""

0 commit comments

Comments
 (0)