11# Copyright 2023 Canonical Ltd.
22# Licensed under the Apache V2, see LICENCE file for details.
33
4- from pathlib import Path
54import unittest
5+ from pathlib import Path
66from unittest import mock
77from mock import patch , Mock , ANY
8+ from typing import Dict , List , Tuple
89
910import yaml
1011
2324 SetAnnotationsChange ,
2425)
2526from juju import charmhub
27+ from juju import constraints
2628from juju .client import client
2729from toposort import CircularDependencyError
2830
@@ -121,14 +123,15 @@ def test_dict_params_missing_data(self):
121123
122124
123125class TestAddApplicationChangeRun :
124-
125126 async def test_run_with_charmhub_charm (self ):
127+ storage_label = "some-label"
128+ storage_constraint = "ebs,100G,1"
126129 change = AddApplicationChange (1 , [], params = {"charm" : "charm" ,
127130 "series" : "series" ,
128131 "application" : "application" ,
129132 "options" : "options" ,
130133 "constraints" : "constraints" ,
131- "storage" : "storage" ,
134+ "storage" : { storage_label : storage_constraint } ,
132135 "endpoint-bindings" : "endpoint_bindings" ,
133136 "resources" : "resources" ,
134137 "devices" : "devices" ,
@@ -160,22 +163,106 @@ async def test_run_with_charmhub_charm(self):
160163 constraints = "constraints" ,
161164 endpoint_bindings = "endpoint_bindings" ,
162165 resources = ["resource1" ],
163- storage = "storage" ,
166+ storage = { storage_label : constraints . parse_storage_constraint ( storage_constraint )} ,
164167 devices = "devices" ,
165168 channel = "channel" ,
166169 charm_origin = ANY ,
167170 num_units = "num_units" )
168171
172+ async def test_run_with_storage_variations (self ):
173+ """Test that various valid storage constraints are parsed as expected before model._deploy is called.
174+
175+ Uses the mock call logic from test_run_with_charmhub_charm, which will run before this test.
176+ """
177+ storage_arg_pairs : List [Tuple [Dict [str , str ], Dict [str , constraints .StorageConstraintDict ]]] = [
178+ # (storage_arg_for_change, storage_arg_for_deploy)
179+ ({'some-label' : 'ebs,100G,1' }, {'some-label' : {'count' : 1 , 'pool' : 'ebs' , 'size' : 102400 }}),
180+ ({'some-label' : 'ebs,2.1G,3' }, {'some-label' : {'count' : 3 , 'pool' : 'ebs' , 'size' : 2150 }}),
181+ ({'some-label' : 'ebs,100G' }, {'some-label' : {'count' : 1 , 'pool' : 'ebs' , 'size' : 102400 }}),
182+ ({'some-label' : 'ebs,2' }, {'some-label' : {'count' : 2 , 'pool' : 'ebs' }}),
183+ ({'some-label' : '200G,7' }, {'some-label' : {'count' : 7 , 'size' : 204800 }}),
184+ ({'some-label' : 'ebs' }, {'some-label' : {'count' : 1 , 'pool' : 'ebs' }}),
185+ ({'some-label' : '10YB' }, {'some-label' : {'count' : 1 , 'size' : 11529215046068469760 }}),
186+ ({'some-label' : '1' }, {'some-label' : {'count' : 1 }}),
187+ ({'some-label' : '-1' }, {'some-label' : {'count' : 1 }}),
188+ ({'some-label' : '' }, {'some-label' : {'count' : 1 }}),
189+ (
190+ {
191+ 'some-label' : '2.1G,3' ,
192+ 'data' : '1MiB,70' ,
193+ 'logs' : 'ebs,-1' ,
194+ },
195+ {
196+ 'some-label' : {'count' : 3 , 'size' : 2150 },
197+ 'data' : {'count' : 70 , 'size' : 1 },
198+ 'logs' : {'count' : 1 , 'pool' : 'ebs' }
199+ },
200+ ),
201+ ]
202+ for storage_arg_for_change , storage_arg_for_deploy in storage_arg_pairs :
203+ change = AddApplicationChange (
204+ 1 ,
205+ [],
206+ params = {
207+ "charm" : "charm" ,
208+ "series" : "series" ,
209+ "application" : "application" ,
210+ "options" : "options" ,
211+ "constraints" : "constraints" ,
212+ "storage" : storage_arg_for_change ,
213+ "endpoint-bindings" : "endpoint_bindings" ,
214+ "resources" : "resources" ,
215+ "devices" : "devices" ,
216+ "num-units" : "num_units" ,
217+ "channel" : "channel" ,
218+ },
219+ )
220+ # mock model
221+ model = Mock ()
222+ model ._deploy = mock .AsyncMock (return_value = None )
223+ model ._add_charmhub_resources = mock .AsyncMock (return_value = ["resource1" ])
224+ model .applications = {}
225+ # mock context
226+ context = Mock ()
227+ context .resolve .return_value = "ch:charm1"
228+ context .origins = {"ch:charm1" : Mock ()}
229+ context .trusted = False
230+ context .model = model
231+ # mock info_func
232+ info_func = mock .AsyncMock (return_value = ["12345" , "name" ])
233+ # patch and call
234+ with patch .object (charmhub .CharmHub , 'get_charm_id' , info_func ):
235+ result = await change .run (context )
236+ assert result == "application"
237+ # asserts
238+ model ._deploy .assert_called_once ()
239+ model ._deploy .assert_called_with (
240+ charm_url = "ch:charm1" ,
241+ application = "application" ,
242+ series = "series" ,
243+ config = "options" ,
244+ constraints = "constraints" ,
245+ endpoint_bindings = "endpoint_bindings" ,
246+ resources = ["resource1" ],
247+ storage = storage_arg_for_deploy , # we're testing this
248+ devices = "devices" ,
249+ channel = "channel" ,
250+ charm_origin = ANY ,
251+ num_units = "num_units" ,
252+ )
253+
169254 async def test_run_with_charmhub_charm_no_channel (self ):
170255 """Test to verify if when the given channel is None, the channel defaults to "local/stable", which
171256 is the default channel value for the Charm Hub
172257 """
258+ storage_label = "some-label"
259+ storage_constraint = "ebs,100G,1"
173260 change = AddApplicationChange (1 , [], params = {"charm" : "charm" ,
174261 "series" : "series" ,
175262 "application" : "application" ,
176263 "options" : "options" ,
177264 "constraints" : "constraints" ,
178- "storage" : "storage" ,
265+ "storage" : { storage_label : storage_constraint } ,
179266 "endpoint-bindings" : "endpoint_bindings" ,
180267 "resources" : "resources" ,
181268 "devices" : "devices" ,
@@ -208,19 +295,21 @@ async def test_run_with_charmhub_charm_no_channel(self):
208295 constraints = "constraints" ,
209296 endpoint_bindings = "endpoint_bindings" ,
210297 resources = ["resource1" ],
211- storage = "storage" ,
298+ storage = { storage_label : constraints . parse_storage_constraint ( storage_constraint )} ,
212299 devices = "devices" ,
213300 channel = "latest/stable" ,
214301 charm_origin = ANY ,
215302 num_units = "num_units" )
216303
217304 async def test_run_local (self ):
305+ storage_label = "some-label"
306+ storage_constraint = "ebs,100G,1"
218307 change = AddApplicationChange (1 , [], params = {"charm" : "local:charm" ,
219308 "series" : "series" ,
220309 "application" : "application" ,
221310 "options" : "options" ,
222311 "constraints" : "constraints" ,
223- "storage" : "storage" ,
312+ "storage" : { storage_label : storage_constraint } ,
224313 "endpoint-bindings" : "endpoint_bindings" ,
225314 "devices" : "devices" ,
226315 "num-units" : "num_units" })
@@ -246,19 +335,21 @@ async def test_run_local(self):
246335 constraints = "constraints" ,
247336 endpoint_bindings = "endpoint_bindings" ,
248337 resources = {},
249- storage = "storage" ,
338+ storage = { storage_label : constraints . parse_storage_constraint ( storage_constraint )} ,
250339 devices = "devices" ,
251340 num_units = "num_units" ,
252341 channel = "" ,
253342 charm_origin = ANY )
254343
255344 async def test_run_no_series (self ):
345+ storage_label = "some-label"
346+ storage_constraint = "ebs,100G,1"
256347 change = AddApplicationChange (1 , [], params = {"charm" : "ch:charm1" ,
257348 "series" : "" ,
258349 "application" : "application" ,
259350 "options" : "options" ,
260351 "constraints" : "constraints" ,
261- "storage" : "storage" ,
352+ "storage" : { storage_label : storage_constraint } ,
262353 "endpoint-bindings" : "endpoint_bindings" ,
263354 "resources" : "resources" ,
264355 "devices" : "devices" ,
@@ -289,7 +380,7 @@ async def test_run_no_series(self):
289380 constraints = "constraints" ,
290381 endpoint_bindings = "endpoint_bindings" ,
291382 resources = ["resource1" ],
292- storage = "storage" ,
383+ storage = { storage_label : constraints . parse_storage_constraint ( storage_constraint )} ,
293384 devices = "devices" ,
294385 channel = "channel" ,
295386 charm_origin = ANY ,
0 commit comments