Skip to content

Commit 95ec1d7

Browse files
authoredMay 10, 2019
Merge pull request #101 from facultyai/enhance-environment-client
Enhance environment client
2 parents e64b911 + 2210d72 commit 95ec1d7

File tree

2 files changed

+627
-14
lines changed

2 files changed

+627
-14
lines changed
 

‎faculty/clients/environment.py

+213-2
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,37 @@
1313
# limitations under the License.
1414

1515

16+
import re
1617
from collections import namedtuple
18+
from enum import Enum
19+
20+
from marshmallow import (
21+
ValidationError,
22+
fields,
23+
post_load,
24+
validates,
25+
post_dump,
26+
)
27+
from marshmallow_enum import EnumField
28+
29+
from faculty.clients.base import BaseClient, BaseSchema
1730

18-
from marshmallow import fields, post_load
1931

20-
from faculty.clients.base import BaseSchema, BaseClient
32+
class Constraint(Enum):
33+
AT_LEAST = ">="
34+
EQUAL = "=="
2135

2236

37+
Version = namedtuple("Version", ["constraint", "identifier"])
38+
PythonPackage = namedtuple("PythonPackage", ["name", "version"])
39+
Pip = namedtuple("Pip", ["extra_index_urls", "packages"])
40+
Conda = namedtuple("Conda", ["channels", "packages"])
41+
PythonEnvironment = namedtuple("PythonEnvironment", ["pip", "conda"])
42+
Apt = namedtuple("Apt", ["packages"])
43+
AptPackage = namedtuple("AptPackage", ["name"])
44+
Script = namedtuple("Script", ["script"])
45+
PythonSpecification = namedtuple("PythonSpecification", ["python2", "python3"])
46+
Specification = namedtuple("Specification", ["apt", "bash", "python"])
2347
Environment = namedtuple(
2448
"Environment",
2549
[
@@ -30,8 +54,139 @@
3054
"author_id",
3155
"created_at",
3256
"updated_at",
57+
"specification",
3358
],
3459
)
60+
EnvironmentCreationResponse = namedtuple("EnvironmentCreationResponse", ["id"])
61+
EnvironmentCreateUpdate = namedtuple(
62+
"EnvironmentCreateUpdate", ["name", "description", "specification"]
63+
)
64+
65+
VERSION_REGEX = re.compile(
66+
r"^(?:\d+\!)?\d+(?:\.\d+)*(?:(?:a|b|rc)\d+)?(?:\.post\d+)?(?:\.dev\d+)?$"
67+
)
68+
69+
70+
class VersionSchema(BaseSchema):
71+
constraint = EnumField(Constraint, by_value=True, required=True)
72+
identifier = fields.String(required=True)
73+
74+
@validates("identifier")
75+
def validate_version_format(self, data):
76+
if not VERSION_REGEX.match(data):
77+
raise ValidationError("Invalid version format")
78+
79+
@post_load
80+
def make_version(self, data):
81+
return Version(**data)
82+
83+
@post_dump
84+
def dump_version(self, data):
85+
self.validate_version_format(data["identifier"])
86+
return data
87+
88+
89+
class VersionField(fields.Field):
90+
"""
91+
Field that serialises/deserialises a Python package version.
92+
"""
93+
94+
def _deserialize(self, value, attr, obj, **kwargs):
95+
if value == "latest":
96+
return "latest"
97+
else:
98+
return VersionSchema().load(value)
99+
100+
def _serialize(self, value, attr, obj, **kwargs):
101+
if value == "latest":
102+
return "latest"
103+
else:
104+
return VersionSchema().dump(value)
105+
106+
107+
class PythonPackageSchema(BaseSchema):
108+
name = fields.String(required=True)
109+
version = VersionField(required=True)
110+
111+
@post_load
112+
def make_python_package(self, data):
113+
return PythonPackage(**data)
114+
115+
116+
class PipSchema(BaseSchema):
117+
extra_index_urls = fields.List(
118+
fields.String(), data_key="extraIndexUrls", required=True
119+
)
120+
packages = fields.List(fields.Nested(PythonPackageSchema()), required=True)
121+
122+
@post_load
123+
def make_pip(self, data):
124+
return Pip(**data)
125+
126+
127+
class CondaSchema(BaseSchema):
128+
channels = fields.List(fields.String(), required=True)
129+
packages = fields.List(fields.Nested(PythonPackageSchema()), required=True)
130+
131+
@post_load
132+
def make_conda(self, data):
133+
return Conda(**data)
134+
135+
136+
class PythonEnvironmentSchema(BaseSchema):
137+
conda = fields.Nested(CondaSchema(), required=True)
138+
pip = fields.Nested(PipSchema(), required=True)
139+
140+
@post_load
141+
def make_python_specification(self, data):
142+
return PythonEnvironment(**data)
143+
144+
145+
class PythonSpecificationSchema(BaseSchema):
146+
python2 = fields.Nested(
147+
PythonEnvironmentSchema(), data_key="Python2", missing=None
148+
)
149+
python3 = fields.Nested(
150+
PythonEnvironmentSchema(), data_key="Python3", missing=None
151+
)
152+
153+
@post_load
154+
def make_python(self, data):
155+
return PythonSpecification(**data)
156+
157+
158+
class AptPackageSchema(BaseSchema):
159+
name = fields.String(required=True)
160+
161+
@post_load
162+
def make_apt_package(self, data):
163+
return AptPackage(**data)
164+
165+
166+
class AptSchema(BaseSchema):
167+
packages = fields.List(fields.Nested(AptPackageSchema()), required=True)
168+
169+
@post_load
170+
def make_apt(self, data):
171+
return Apt(**data)
172+
173+
174+
class ScriptSchema(BaseSchema):
175+
script = fields.String(required=True)
176+
177+
@post_load
178+
def make_script(self, data):
179+
return Script(**data)
180+
181+
182+
class SpecificationSchema(BaseSchema):
183+
apt = fields.Nested(AptSchema(), required=True)
184+
bash = fields.List(fields.Nested(ScriptSchema()), required=True)
185+
python = fields.Nested(PythonSpecificationSchema(), required=True)
186+
187+
@post_load
188+
def make_specification(self, data):
189+
return Specification(**data)
35190

36191

37192
class EnvironmentSchema(BaseSchema):
@@ -42,16 +197,72 @@ class EnvironmentSchema(BaseSchema):
42197
author_id = fields.UUID(data_key="authorId", required=True)
43198
created_at = fields.DateTime(data_key="createdAt", required=True)
44199
updated_at = fields.DateTime(data_key="updatedAt", required=True)
200+
specification = fields.Nested(SpecificationSchema(), required=True)
45201

46202
@post_load
47203
def make_environment(self, data):
48204
return Environment(**data)
49205

50206

207+
class EnvironmentCreateUpdateSchema(BaseSchema):
208+
name = fields.String(required=True)
209+
description = fields.String(required=True, allow_none=True)
210+
specification = fields.Nested(SpecificationSchema(), required=True)
211+
212+
@post_load
213+
def make_environment_update(self, data):
214+
return EnvironmentCreateUpdate(**data)
215+
216+
217+
class EnvironmentCreationResponseSchema(BaseSchema):
218+
id = fields.UUID(data_key="environmentId", required=True)
219+
220+
@post_load
221+
def make_environment(self, data):
222+
return EnvironmentCreationResponse(**data)
223+
224+
51225
class EnvironmentClient(BaseClient):
52226

53227
SERVICE_NAME = "baskerville"
54228

55229
def list(self, project_id):
56230
endpoint = "/project/{}/environment".format(project_id)
57231
return self._get(endpoint, EnvironmentSchema(many=True))
232+
233+
def get(self, project_id, environment_id):
234+
endpoint = "/project/{}/environment/{}".format(
235+
project_id, environment_id
236+
)
237+
return self._get(endpoint, EnvironmentSchema())
238+
239+
def update(
240+
self, project_id, environment_id, name, specification, description=None
241+
):
242+
content = EnvironmentCreateUpdate(
243+
name=name, specification=specification, description=description
244+
)
245+
endpoint = "/project/{}/environment/{}".format(
246+
project_id, environment_id
247+
)
248+
self._put_raw(
249+
endpoint, json=EnvironmentCreateUpdateSchema().dump(content)
250+
)
251+
252+
def create(self, project_id, name, specification, description=None):
253+
endpoint = "/project/{}/environment".format(project_id)
254+
content = EnvironmentCreateUpdate(
255+
name=name, specification=specification, description=description
256+
)
257+
response = self._post(
258+
endpoint,
259+
EnvironmentCreationResponseSchema(),
260+
json=EnvironmentCreateUpdateSchema().dump(content),
261+
)
262+
return response.id
263+
264+
def delete(self, project_id, environment_id):
265+
endpoint = "/project/{}/environment/{}".format(
266+
project_id, environment_id
267+
)
268+
self._delete_raw(endpoint)

‎tests/clients/test_environment.py

+414-12
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,362 @@
2020
from marshmallow import ValidationError
2121

2222
from faculty.clients.environment import (
23+
Apt,
24+
AptPackage,
25+
AptPackageSchema,
26+
AptSchema,
27+
Conda,
28+
CondaSchema,
29+
Constraint,
2330
Environment,
2431
EnvironmentClient,
32+
EnvironmentCreateUpdate,
33+
EnvironmentCreateUpdateSchema,
34+
EnvironmentCreationResponse,
35+
EnvironmentCreationResponseSchema,
2536
EnvironmentSchema,
37+
Pip,
38+
PipSchema,
39+
PythonSpecification,
40+
PythonPackage,
41+
PythonPackageSchema,
42+
PythonSpecificationSchema,
43+
PythonEnvironment,
44+
PythonEnvironmentSchema,
45+
Script,
46+
ScriptSchema,
47+
Specification,
48+
SpecificationSchema,
49+
Version,
50+
VersionSchema,
2651
)
2752

53+
VERSION_BODY = {"constraint": "==", "identifier": "1.0.0"}
54+
VERSION = Version(constraint=Constraint.EQUAL, identifier="1.0.0")
55+
56+
VERSION_BODY_LATEST = "latest"
57+
VERSION_LATEST = "latest"
58+
59+
INVALID_VERSION_BODY = {"constraint": "==", "identifier": "invalid-identifier"}
60+
INVALID_VERSION = Version(
61+
constraint=Constraint.EQUAL, identifier="invalid-identifier"
62+
)
63+
64+
PYTHON_PACKAGE_BODY = {"name": "tensorflow", "version": VERSION_BODY}
65+
PYTHON_PACKAGE = PythonPackage(name="tensorflow", version=VERSION)
66+
67+
PYTHON_PACKAGE_BODY_LATEST = {
68+
"name": "tensorflow",
69+
"version": VERSION_BODY_LATEST,
70+
}
71+
PYTHON_PACKAGE_LATEST = PythonPackage(
72+
name="tensorflow", version=VERSION_LATEST
73+
)
74+
75+
76+
PIP_BODY = {
77+
"extraIndexUrls": ["http://example.com/"],
78+
"packages": [PYTHON_PACKAGE_BODY],
79+
}
80+
PIP = Pip(extra_index_urls=["http://example.com/"], packages=[PYTHON_PACKAGE])
81+
82+
83+
CONDA_BODY = {"channels": ["conda-forge"], "packages": [PYTHON_PACKAGE_BODY]}
84+
CONDA = Conda(channels=["conda-forge"], packages=[PYTHON_PACKAGE])
85+
86+
PYTHON_ENVIRONMENT_BODY = {"pip": PIP_BODY, "conda": CONDA_BODY}
87+
PYTHON_ENVIRONMENT = PythonEnvironment(conda=CONDA, pip=PIP)
88+
89+
APT_PACKAGE_BODY = {"name": "cuda"}
90+
APT_PACKAGE = AptPackage(name="cuda")
91+
92+
APT_BODY = {"packages": [APT_PACKAGE_BODY]}
93+
APT = Apt(packages=[APT_PACKAGE])
94+
95+
PYTHON_SPECIFICATION_BODY = {
96+
"Python2": PYTHON_ENVIRONMENT_BODY,
97+
"Python3": PYTHON_ENVIRONMENT_BODY,
98+
}
99+
PYTHON_SPECIFICATION = PythonSpecification(
100+
python2=PYTHON_ENVIRONMENT, python3=PYTHON_ENVIRONMENT
101+
)
102+
103+
PYTHON_SPECIFICATION_BODY_MISSING_PYTHON2 = {
104+
"Python3": PYTHON_ENVIRONMENT_BODY
105+
}
106+
PYTHON_SPECIFICATION_BODY_PYTHON2_NONE = {
107+
"Python2": None,
108+
"Python3": PYTHON_ENVIRONMENT_BODY,
109+
}
110+
PYTHON_SPECIFICATION_PYTHON2_NONE = PythonSpecification(
111+
python2=None, python3=PYTHON_ENVIRONMENT
112+
)
113+
114+
PYTHON_SPECIFICATION_BODY_MISSING_PYTHON3 = {
115+
"Python2": PYTHON_ENVIRONMENT_BODY
116+
}
117+
PYTHON_SPECIFICATION_BODY_PYTHON3_NONE = {
118+
"Python2": PYTHON_ENVIRONMENT_BODY,
119+
"Python3": None,
120+
}
121+
PYTHON_SPECIFICATION_PYTHON3_NONE = PythonSpecification(
122+
python2=PYTHON_ENVIRONMENT, python3=None
123+
)
124+
125+
PYTHON_SPECIFICATION_BODY_MISSING_KEYS = {}
126+
PYTHON_SPECIFICATION_BODY_NONE = {"Python2": None, "Python3": None}
127+
PYTHON_SPECIFICATION_NONE = PythonSpecification(python2=None, python3=None)
128+
129+
SCRIPT_STR = "# Edit your script\n"
130+
SCRIPT_BODY = {"script": SCRIPT_STR}
131+
SCRIPT = Script(script=SCRIPT_STR)
132+
133+
SPECIFICATION_BODY = {
134+
"apt": APT_BODY,
135+
"bash": [SCRIPT_BODY],
136+
"python": PYTHON_SPECIFICATION_BODY,
137+
}
138+
SPECIFICATION = Specification(
139+
apt=APT, bash=[SCRIPT], python=PYTHON_SPECIFICATION
140+
)
28141

29142
PROJECT_ID = uuid.uuid4()
143+
ENVIRONMENT_ID = uuid.uuid4()
144+
AUTHOR_ID = uuid.uuid4()
145+
NAME = "Test environment"
146+
DESCRIPTION = "Environment description"
30147

148+
ENVIRONMENT_BODY = {
149+
"environmentId": str(ENVIRONMENT_ID),
150+
"projectId": str(PROJECT_ID),
151+
"name": NAME,
152+
"description": DESCRIPTION,
153+
"authorId": str(AUTHOR_ID),
154+
"createdAt": "2018-10-03T04:20:00Z",
155+
"updatedAt": "2018-11-03T04:21:15Z",
156+
"specification": SPECIFICATION_BODY,
157+
}
31158
ENVIRONMENT = Environment(
32-
id=uuid.uuid4(),
159+
id=ENVIRONMENT_ID,
33160
project_id=PROJECT_ID,
34-
name="Test Environment",
35-
description="Environment description",
36-
author_id=uuid.uuid4(),
161+
name=NAME,
162+
description=DESCRIPTION,
163+
author_id=AUTHOR_ID,
37164
created_at=datetime.datetime(2018, 10, 3, 4, 20, 0, 0, tzinfo=UTC),
38165
updated_at=datetime.datetime(2018, 11, 3, 4, 21, 15, 0, tzinfo=UTC),
166+
specification=SPECIFICATION,
39167
)
40168

41-
ENVIRONMENT_BODY = {
42-
"environmentId": str(ENVIRONMENT.id),
43-
"projectId": str(PROJECT_ID),
44-
"name": ENVIRONMENT.name,
45-
"description": ENVIRONMENT.description,
46-
"authorId": str(ENVIRONMENT.author_id),
47-
"createdAt": "2018-10-03T04:20:00Z",
48-
"updatedAt": "2018-11-03T04:21:15Z",
169+
ENVIRONMENT_CREATION_RESPONSE_BODY = {"environmentId": str(ENVIRONMENT_ID)}
170+
ENVIRONMENT_CREATION_RESPONSE = EnvironmentCreationResponse(id=ENVIRONMENT_ID)
171+
172+
ENVIRONMENT_CREATE_UPDATE_BODY = {
173+
"name": NAME,
174+
"description": DESCRIPTION,
175+
"specification": SPECIFICATION_BODY,
176+
}
177+
ENVIRONMENT_CREATE_UPDATE = EnvironmentCreateUpdate(
178+
name=NAME, description=DESCRIPTION, specification=SPECIFICATION
179+
)
180+
ENVIRONMENT_CREATE_UPDATE_BODY_NO_DESCRIPTION = {
181+
"name": NAME,
182+
"description": None,
183+
"specification": SPECIFICATION_BODY,
49184
}
185+
ENVIRONMENT_CREATE_UPDATE_NO_DESCRIPTION = EnvironmentCreateUpdate(
186+
name=NAME, description=None, specification=SPECIFICATION
187+
)
188+
189+
190+
def test_version_schema_load():
191+
data = VersionSchema().load(VERSION_BODY)
192+
assert data == VERSION
193+
194+
195+
def test_version_schema_dump():
196+
data = VersionSchema().dump(VERSION)
197+
assert data == VERSION_BODY
198+
199+
200+
def test_version_schema_load_invalid():
201+
with pytest.raises(ValidationError):
202+
VersionSchema().load(INVALID_VERSION_BODY)
203+
204+
205+
def test_version_schema_dump_invalid():
206+
with pytest.raises(ValidationError):
207+
VersionSchema().dump(INVALID_VERSION)
208+
209+
210+
@pytest.mark.parametrize(
211+
"body, expected",
212+
[
213+
(PYTHON_PACKAGE_BODY, PYTHON_PACKAGE),
214+
(PYTHON_PACKAGE_BODY_LATEST, PYTHON_PACKAGE_LATEST),
215+
],
216+
)
217+
def test_python_package_schema_load(body, expected):
218+
data = PythonPackageSchema().load(body)
219+
assert data == expected
220+
221+
222+
@pytest.mark.parametrize(
223+
"object, expected",
224+
[
225+
(PYTHON_PACKAGE, PYTHON_PACKAGE_BODY),
226+
(PYTHON_PACKAGE_LATEST, PYTHON_PACKAGE_BODY_LATEST),
227+
],
228+
)
229+
def test_python_package_schema_dump(object, expected):
230+
data = PythonPackageSchema().dump(object)
231+
assert data == expected
232+
233+
234+
def test_pip_schema_load():
235+
data = PipSchema().load(PIP_BODY)
236+
assert data == PIP
237+
238+
239+
def test_pip_schema_dump():
240+
data = PipSchema().dump(PIP)
241+
assert data == PIP_BODY
242+
243+
244+
def test_conda_schema_load():
245+
data = CondaSchema().load(CONDA_BODY)
246+
assert data == CONDA
247+
248+
249+
def test_conda_schema_dump():
250+
data = CondaSchema().dump(CONDA)
251+
assert data == CONDA_BODY
252+
253+
254+
def test_python_environment_schema_load():
255+
data = PythonEnvironmentSchema().load(PYTHON_ENVIRONMENT_BODY)
256+
assert data == PYTHON_ENVIRONMENT
257+
258+
259+
def test_python_environment_schema_dump():
260+
data = PythonEnvironmentSchema().dump(PYTHON_ENVIRONMENT)
261+
assert data == PYTHON_ENVIRONMENT_BODY
262+
263+
264+
def test_apt_package_schema_load():
265+
data = AptPackageSchema().load(APT_PACKAGE_BODY)
266+
assert data == APT_PACKAGE
267+
268+
269+
def test_apt_package_schema_dump():
270+
data = AptPackageSchema().dump(APT_PACKAGE)
271+
assert data == APT_PACKAGE_BODY
272+
273+
274+
def test_apt_schema_load():
275+
data = AptSchema().load(APT_BODY)
276+
assert data == APT
277+
278+
279+
def test_apt_schema_dump():
280+
data = AptSchema().dump(APT)
281+
assert data == APT_BODY
282+
283+
284+
@pytest.mark.parametrize(
285+
"body, expected",
286+
[
287+
(PYTHON_SPECIFICATION_BODY, PYTHON_SPECIFICATION),
288+
(
289+
PYTHON_SPECIFICATION_BODY_MISSING_PYTHON2,
290+
PYTHON_SPECIFICATION_PYTHON2_NONE,
291+
),
292+
(
293+
PYTHON_SPECIFICATION_BODY_MISSING_PYTHON3,
294+
PYTHON_SPECIFICATION_PYTHON3_NONE,
295+
),
296+
(PYTHON_SPECIFICATION_BODY_MISSING_KEYS, PYTHON_SPECIFICATION_NONE),
297+
],
298+
)
299+
def test_python_specification_schema_load(body, expected):
300+
data = PythonSpecificationSchema().load(body)
301+
assert data == expected
302+
303+
304+
@pytest.mark.parametrize(
305+
"object, expected",
306+
[
307+
(PYTHON_SPECIFICATION, PYTHON_SPECIFICATION_BODY),
308+
(
309+
PYTHON_SPECIFICATION_PYTHON2_NONE,
310+
PYTHON_SPECIFICATION_BODY_PYTHON2_NONE,
311+
),
312+
(
313+
PYTHON_SPECIFICATION_PYTHON3_NONE,
314+
PYTHON_SPECIFICATION_BODY_PYTHON3_NONE,
315+
),
316+
(PYTHON_SPECIFICATION_NONE, PYTHON_SPECIFICATION_BODY_NONE),
317+
],
318+
)
319+
def test_python_specification_schema_dump(object, expected):
320+
data = PythonSpecificationSchema().dump(object)
321+
assert data == expected
322+
323+
324+
def test_script_schema_load():
325+
data = ScriptSchema().load(SCRIPT_BODY)
326+
assert data == SCRIPT
327+
328+
329+
def test_script_schema_dump():
330+
data = ScriptSchema().dump(SCRIPT)
331+
assert data == SCRIPT_BODY
332+
333+
334+
def test_specification_schema_load():
335+
data = SpecificationSchema().load(SPECIFICATION_BODY)
336+
assert data == SPECIFICATION
337+
338+
339+
def test_specification_schema_dump():
340+
data = SpecificationSchema().dump(SPECIFICATION)
341+
assert data == SPECIFICATION_BODY
342+
343+
344+
@pytest.mark.parametrize(
345+
"body, expected",
346+
[
347+
(ENVIRONMENT_CREATE_UPDATE_BODY, ENVIRONMENT_CREATE_UPDATE),
348+
(
349+
ENVIRONMENT_CREATE_UPDATE_BODY_NO_DESCRIPTION,
350+
ENVIRONMENT_CREATE_UPDATE_NO_DESCRIPTION,
351+
),
352+
],
353+
)
354+
def test_environment_create_update_schema_load(body, expected):
355+
data = EnvironmentCreateUpdateSchema().load(body)
356+
assert data == expected
357+
358+
359+
@pytest.mark.parametrize(
360+
"object, expected",
361+
[
362+
(ENVIRONMENT_CREATE_UPDATE, ENVIRONMENT_CREATE_UPDATE_BODY),
363+
(
364+
ENVIRONMENT_CREATE_UPDATE_NO_DESCRIPTION,
365+
ENVIRONMENT_CREATE_UPDATE_BODY_NO_DESCRIPTION,
366+
),
367+
],
368+
)
369+
def test_environment_create_update_schema_dump(object, expected):
370+
data = EnvironmentCreateUpdateSchema().dump(object)
371+
assert data == expected
372+
373+
374+
def test_environment_creation_response_schema():
375+
data = EnvironmentCreationResponseSchema().load(
376+
ENVIRONMENT_CREATION_RESPONSE_BODY
377+
)
378+
assert data == ENVIRONMENT_CREATION_RESPONSE
50379

51380

52381
def test_environment_schema():
@@ -70,3 +399,76 @@ def test_environment_client_list(mocker):
70399
EnvironmentClient._get.assert_called_once_with(
71400
"/project/{}/environment".format(PROJECT_ID), schema_mock.return_value
72401
)
402+
403+
404+
def test_environment_client_get(mocker):
405+
mocker.patch.object(EnvironmentClient, "_get", return_value=ENVIRONMENT)
406+
schema_mock = mocker.patch("faculty.clients.environment.EnvironmentSchema")
407+
408+
client = EnvironmentClient(mocker.Mock())
409+
assert client.get(PROJECT_ID, ENVIRONMENT_ID) == ENVIRONMENT
410+
411+
schema_mock.assert_called_once_with()
412+
EnvironmentClient._get.assert_called_once_with(
413+
"/project/{}/environment/{}".format(PROJECT_ID, ENVIRONMENT_ID),
414+
schema_mock.return_value,
415+
)
416+
417+
418+
def test_environment_client_update(mocker):
419+
mocker.patch.object(EnvironmentClient, "_put_raw")
420+
mocker.patch.object(
421+
EnvironmentCreateUpdateSchema,
422+
"dump",
423+
return_value=ENVIRONMENT_CREATE_UPDATE_BODY,
424+
)
425+
426+
client = EnvironmentClient(mocker.Mock())
427+
client.update(PROJECT_ID, ENVIRONMENT_ID, NAME, SPECIFICATION, DESCRIPTION)
428+
429+
EnvironmentCreateUpdateSchema.dump.assert_called_once_with(
430+
ENVIRONMENT_CREATE_UPDATE
431+
)
432+
EnvironmentClient._put_raw.assert_called_once_with(
433+
"/project/{}/environment/{}".format(PROJECT_ID, ENVIRONMENT_ID),
434+
json=ENVIRONMENT_CREATE_UPDATE_BODY,
435+
)
436+
437+
438+
def test_environment_client_create(mocker):
439+
mocker.patch.object(
440+
EnvironmentClient, "_post", return_value=ENVIRONMENT_CREATION_RESPONSE
441+
)
442+
mocker.patch.object(
443+
EnvironmentCreateUpdateSchema,
444+
"dump",
445+
return_value=ENVIRONMENT_CREATE_UPDATE_BODY,
446+
)
447+
schema_mock = mocker.patch(
448+
"faculty.clients.environment.EnvironmentCreationResponseSchema"
449+
)
450+
451+
client = EnvironmentClient(mocker.Mock())
452+
assert (
453+
client.create(PROJECT_ID, NAME, SPECIFICATION, description=DESCRIPTION)
454+
== ENVIRONMENT_CREATION_RESPONSE.id
455+
)
456+
EnvironmentCreateUpdateSchema.dump.assert_called_once_with(
457+
ENVIRONMENT_CREATE_UPDATE
458+
)
459+
EnvironmentClient._post.assert_called_once_with(
460+
"/project/{}/environment".format(PROJECT_ID),
461+
schema_mock.return_value,
462+
json=ENVIRONMENT_CREATE_UPDATE_BODY,
463+
)
464+
465+
466+
def test_environment_client_delete(mocker):
467+
mocker.patch.object(EnvironmentClient, "_delete_raw", return_value=None)
468+
469+
client = EnvironmentClient(mocker.Mock())
470+
client.delete(PROJECT_ID, ENVIRONMENT_ID)
471+
472+
EnvironmentClient._delete_raw.assert_called_once_with(
473+
"/project/{}/environment/{}".format(PROJECT_ID, ENVIRONMENT_ID)
474+
)

0 commit comments

Comments
 (0)
Please sign in to comment.