diff --git a/moto/greengrass/models.py b/moto/greengrass/models.py index def34fe76..baaa701ae 100644 --- a/moto/greengrass/models.py +++ b/moto/greengrass/models.py @@ -101,7 +101,7 @@ class FakeDeviceDefinitionVersion(BaseModel): self.device_definition_id = device_definition_id self.devices = devices self.version = str(uuid.uuid4()) - self.arn = f"arn:aws:greengrass:{region_name}:{get_account_id()}:/greengrass/definition/devices/{self.device_definition_id}/versions/{self.version}" + self.arn = f"arn:aws:greengrass:{region_name}:{get_account_id()}:greengrass/definition/devices/{self.device_definition_id}/versions/{self.version}" self.created_at_datetime = datetime.utcnow() def to_dict(self, include_detail=False): @@ -124,7 +124,7 @@ class FakeResourceDefinition(BaseModel): def __init__(self, region_name, name, initial_version): self.region_name = region_name self.id = str(uuid.uuid4()) - self.arn = f"arn:aws:greengrass:{region_name}:{get_account_id()}:/greengrass/definition/resources/{self.id}" + self.arn = f"arn:aws:greengrass:{region_name}:{get_account_id()}:greengrass/definition/resources/{self.id}" self.created_at_datetime = datetime.utcnow() self.update_at_datetime = datetime.utcnow() self.latest_version = "" @@ -154,7 +154,7 @@ class FakeResourceDefinitionVersion(BaseModel): self.resource_definition_id = resource_definition_id self.resources = resources self.version = str(uuid.uuid4()) - self.arn = f"arn:aws:greengrass:{region_name}:{get_account_id()}:/greengrass/definition/resources/{self.resource_definition_id}/versions/{self.version}" + self.arn = f"arn:aws:greengrass:{region_name}:{get_account_id()}:greengrass/definition/resources/{self.resource_definition_id}/versions/{self.version}" self.created_at_datetime = datetime.utcnow() def to_dict(self): @@ -173,7 +173,7 @@ class FakeFunctionDefinition(BaseModel): def __init__(self, region_name, name, initial_version): self.region_name = region_name self.id = str(uuid.uuid4()) - self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:/greengrass/definition/functions/{self.id}" + self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:greengrass/definition/functions/{self.id}" self.created_at_datetime = datetime.utcnow() self.update_at_datetime = datetime.utcnow() self.latest_version = "" @@ -206,7 +206,7 @@ class FakeFunctionDefinitionVersion(BaseModel): self.functions = functions self.default_config = default_config self.version = str(uuid.uuid4()) - self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:/greengrass/definition/functions/{self.function_definition_id}/versions/{self.version}" + self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:greengrass/definition/functions/{self.function_definition_id}/versions/{self.version}" self.created_at_datetime = datetime.utcnow() def to_dict(self): @@ -225,7 +225,7 @@ class FakeSubscriptionDefinition(BaseModel): def __init__(self, region_name, name, initial_version): self.region_name = region_name self.id = str(uuid.uuid4()) - self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:/greengrass/definition/subscriptions/{self.id}" + self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:greengrass/definition/subscriptions/{self.id}" self.created_at_datetime = datetime.utcnow() self.update_at_datetime = datetime.utcnow() self.latest_version = "" @@ -255,7 +255,7 @@ class FakeSubscriptionDefinitionVersion(BaseModel): self.subscription_definition_id = subscription_definition_id self.subscriptions = subscriptions self.version = str(uuid.uuid4()) - self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:/greengrass/definition/subscriptions/{self.subscription_definition_id}/versions/{self.version}" + self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:greengrass/definition/subscriptions/{self.subscription_definition_id}/versions/{self.version}" self.created_at_datetime = datetime.utcnow() def to_dict(self): @@ -270,6 +270,97 @@ class FakeSubscriptionDefinitionVersion(BaseModel): } +class FakeGroup(BaseModel): + def __init__(self, region_name, name): + self.region_name = region_name + self.group_id = str(uuid.uuid4()) + self.name = name + self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:greengrass/groups/{self.group_id}" + self.created_at_datetime = datetime.utcnow() + self.last_updated_datetime = datetime.utcnow() + self.latest_version = "" + self.latest_version_arn = "" + + def to_dict(self): + obj = { + "Arn": self.arn, + "CreationTimestamp": iso_8601_datetime_with_milliseconds( + self.created_at_datetime + ), + "Id": self.group_id, + "LastUpdatedTimestamp": iso_8601_datetime_with_milliseconds( + self.last_updated_datetime + ), + "LatestVersion": self.latest_version, + "LatestVersionArn": self.latest_version_arn, + "Name": self.name, + } + return obj + + +class FakeGroupVersion(BaseModel): + def __init__( + self, + region_name, + group_id, + core_definition_version_arn, + device_definition_version_arn, + function_definition_version_arn, + resource_definition_version_arn, + subscription_definition_version_arn, + ): + self.region_name = region_name + self.group_id = group_id + self.version = str(uuid.uuid4()) + self.arn = f"arn:aws:greengrass:{self.region_name}:{get_account_id()}:greengrass/groups/{self.group_id}/versions/{self.version}" + self.created_at_datetime = datetime.utcnow() + self.core_definition_version_arn = core_definition_version_arn + self.device_definition_version_arn = device_definition_version_arn + self.function_definition_version_arn = function_definition_version_arn + self.resource_definition_version_arn = resource_definition_version_arn + self.subscription_definition_version_arn = subscription_definition_version_arn + + def to_dict(self, include_detail=False): + + definition = {} + if self.core_definition_version_arn: + definition["CoreDefinitionVersionArn"] = self.core_definition_version_arn + + if self.device_definition_version_arn: + definition[ + "DeviceDefinitionVersionArn" + ] = self.device_definition_version_arn + + if self.function_definition_version_arn: + definition[ + "FunctionDefinitionVersionArn" + ] = self.function_definition_version_arn + + if self.resource_definition_version_arn: + definition[ + "ResourceDefinitionVersionArn" + ] = self.resource_definition_version_arn + + if self.subscription_definition_version_arn: + definition[ + "SubscriptionDefinitionVersionArn" + ] = self.subscription_definition_version_arn + + obj = { + "Arn": self.arn, + "CreationTimestamp": iso_8601_datetime_with_milliseconds( + self.created_at_datetime + ), + "Id": self.group_id, + "Version": self.version, + } + + if include_detail: + obj["Definition"] = definition + + return obj + + class GreengrassBackend(BaseBackend): def __init__(self, region_name, account_id): super().__init__(region_name, account_id) @@ -790,5 +881,190 @@ class GreengrassBackend(BaseBackend): subscription_definition_version_id ] + def create_group(self, name, initial_version): + group = FakeGroup(self.region_name, name) + self.groups[group.group_id] = group + + definitions = initial_version or {} + core_definition_version_arn = definitions.get("CoreDefinitionVersionArn") + device_definition_version_arn = definitions.get("DeviceDefinitionVersionArn") + function_definition_version_arn = definitions.get( + "FunctionDefinitionVersionArn" + ) + resource_definition_version_arn = definitions.get( + "ResourceDefinitionVersionArn" + ) + subscription_definition_version_arn = definitions.get( + "SubscriptionDefinitionVersionArn" + ) + + self.create_group_version( + group.group_id, + core_definition_version_arn=core_definition_version_arn, + device_definition_version_arn=device_definition_version_arn, + function_definition_version_arn=function_definition_version_arn, + resource_definition_version_arn=resource_definition_version_arn, + subscription_definition_version_arn=subscription_definition_version_arn, + ) + + return group + + def list_groups(self): + return self.groups.values() + + def get_group(self, group_id): + if group_id not in self.groups: + raise IdNotFoundException("That Group Definition does not exist.") + return self.groups.get(group_id) + + def delete_group(self, group_id): + if group_id not in self.groups: + # I don't know why, the error message is different between get_group and delete_group + raise IdNotFoundException("That group definition does not exist.") + del self.groups[group_id] + del self.group_versions[group_id] + + def update_group(self, group_id, name): + + if name == "": + raise InvalidContainerDefinitionException( + "Input does not contain any attributes to be updated" + ) + if group_id not in self.groups: + raise IdNotFoundException("That group definition does not exist.") + self.groups[group_id].name = name + + def create_group_version( + self, + group_id, + core_definition_version_arn, + device_definition_version_arn, + function_definition_version_arn, + resource_definition_version_arn, + subscription_definition_version_arn, + ): + + if group_id not in self.groups: + raise IdNotFoundException("That group does not exist.") + + self._validate_group_version_definitions( + core_definition_version_arn=core_definition_version_arn, + device_definition_version_arn=device_definition_version_arn, + function_definition_version_arn=function_definition_version_arn, + resource_definition_version_arn=resource_definition_version_arn, + subscription_definition_version_arn=subscription_definition_version_arn, + ) + + group_ver = FakeGroupVersion( + self.region_name, + group_id=group_id, + core_definition_version_arn=core_definition_version_arn, + device_definition_version_arn=device_definition_version_arn, + function_definition_version_arn=function_definition_version_arn, + resource_definition_version_arn=resource_definition_version_arn, + subscription_definition_version_arn=subscription_definition_version_arn, + ) + group_vers = self.group_versions.get(group_ver.group_id, {}) + group_vers[group_ver.version] = group_ver + self.group_versions[group_ver.group_id] = group_vers + self.groups[group_id].latest_version_arn = group_ver.arn + self.groups[group_id].latest_version = group_ver.version + return group_ver + + def _validate_group_version_definitions( + self, + core_definition_version_arn=None, + device_definition_version_arn=None, + function_definition_version_arn=None, + resource_definition_version_arn=None, + subscription_definition_version_arn=None, + ): + def _is_valid_def_ver_arn(definition_version_arn, kind="cores"): + + if definition_version_arn is None: + return True + + if kind == "cores": + versions = self.core_definition_versions + elif kind == "devices": + versions = self.device_definition_versions + elif kind == "functions": + versions = self.function_definition_versions + elif kind == "resources": + versions = self.resource_definition_versions + elif kind == "subscriptions": + versions = self.subscription_definition_versions + else: + raise Exception("invalid args") + + arn_regex = ( + r"^arn:aws:greengrass:[a-zA-Z0-9-]+:[0-9]{12}:greengrass/definition/" + + kind + + r"/[a-z0-9-]{36}/versions/[a-z0-9-]{36}$" + ) + + if not re.match(arn_regex, definition_version_arn): + return False + + definition_id = definition_version_arn.split("/")[-3] + + if definition_id not in versions: + return False + + definition_version_id = definition_version_arn.split("/")[-1] + if definition_version_id not in versions[definition_id]: + return False + + if ( + versions[definition_id][definition_version_id].arn + != definition_version_arn + ): + return False + + return True + + errors = [] + + if not _is_valid_def_ver_arn(core_definition_version_arn, kind="cores"): + errors.append("Cores definition reference does not exist") + + if not _is_valid_def_ver_arn(function_definition_version_arn, kind="functions"): + errors.append("Lambda definition reference does not exist") + + if not _is_valid_def_ver_arn(resource_definition_version_arn, kind="resources"): + errors.append("Resource definition reference does not exist") + + if not _is_valid_def_ver_arn(device_definition_version_arn, kind="devices"): + errors.append("Devices definition reference does not exist") + + if not _is_valid_def_ver_arn( + subscription_definition_version_arn, kind="subscriptions" + ): + errors.append("Subscription definition reference does not exist") + + if errors: + error_details = ", ".join(errors) + raise GreengrassClientError( + "400", + f"The group is invalid or corrupted. (ErrorDetails: [{error_details}])", + ) + + def list_group_versions(self, group_id): + if group_id not in self.group_versions: + raise IdNotFoundException("That group definition does not exist.") + return self.group_versions[group_id].values() + + def get_group_version(self, group_id, group_version_id): + + if group_id not in self.group_versions: + raise IdNotFoundException("That group definition does not exist.") + + if group_version_id not in self.group_versions[group_id]: + raise VersionNotFoundException( + f"Version {group_version_id} of Group Definition {group_id} does not exist." + ) + + return self.group_versions[group_id][group_version_id] + greengrass_backends = BackendDict(GreengrassBackend, "greengrass") diff --git a/moto/greengrass/responses.py b/moto/greengrass/responses.py index 4ffb3b864..78b993d36 100644 --- a/moto/greengrass/responses.py +++ b/moto/greengrass/responses.py @@ -558,3 +558,122 @@ class GreengrassResponse(BaseResponse): subscription_definition_version_id=subscription_definition_version_id, ) return 200, {"status": 200}, json.dumps(res.to_dict()) + + def groups(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + + if self.method == "POST": + return self.create_group() + + if self.method == "GET": + return self.list_groups() + + def create_group(self): + + initial_version = self._get_param("InitialVersion") + name = self._get_param("Name") + res = self.greengrass_backend.create_group( + name=name, initial_version=initial_version + ) + return 201, {"status": 201}, json.dumps(res.to_dict()) + + def list_groups(self): + + res = self.greengrass_backend.list_groups() + return ( + 200, + {"status": 200}, + json.dumps({"Groups": [group.to_dict() for group in res]}), + ) + + def group(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + + if self.method == "GET": + return self.get_group() + + if self.method == "DELETE": + return self.delete_group() + + if self.method == "PUT": + return self.update_group() + + def get_group(self): + group_id = self.path.split("/")[-1] + res = self.greengrass_backend.get_group( + group_id=group_id, + ) + return 200, {"status": 200}, json.dumps(res.to_dict()) + + def delete_group(self): + group_id = self.path.split("/")[-1] + self.greengrass_backend.delete_group( + group_id=group_id, + ) + return 200, {"status": 200}, json.dumps({}) + + def update_group(self): + group_id = self.path.split("/")[-1] + name = self._get_param("Name") + self.greengrass_backend.update_group(group_id=group_id, name=name) + return 200, {"status": 200}, json.dumps({}) + + def group_versions(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + + if self.method == "POST": + return self.create_group_version() + + if self.method == "GET": + return self.list_group_versions() + + def create_group_version(self): + + group_id = self.path.split("/")[-2] + + core_definition_version_arn = self._get_param("CoreDefinitionVersionArn") + device_definition_version_arn = self._get_param("DeviceDefinitionVersionArn") + function_definition_version_arn = self._get_param( + "FunctionDefinitionVersionArn" + ) + resource_definition_version_arn = self._get_param( + "ResourceDefinitionVersionArn" + ) + subscription_definition_version_arn = self._get_param( + "SubscriptionDefinitionVersionArn" + ) + + res = self.greengrass_backend.create_group_version( + group_id=group_id, + core_definition_version_arn=core_definition_version_arn, + device_definition_version_arn=device_definition_version_arn, + function_definition_version_arn=function_definition_version_arn, + resource_definition_version_arn=resource_definition_version_arn, + subscription_definition_version_arn=subscription_definition_version_arn, + ) + return 201, {"status": 201}, json.dumps(res.to_dict()) + + def list_group_versions(self): + group_id = self.path.split("/")[-2] + res = self.greengrass_backend.list_group_versions(group_id=group_id) + return ( + 200, + {"status": 200}, + json.dumps({"Versions": [group_ver.to_dict() for group_ver in res]}), + ) + + def group_version(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + + if self.method == "GET": + return self.get_group_version() + + def get_group_version(self): + + group_id = self.path.split("/")[-3] + group_version_id = self.path.split("/")[-1] + res = self.greengrass_backend.get_group_version( + group_id=group_id, + group_version_id=group_version_id, + ) + return 200, {"status": 200}, json.dumps(res.to_dict(include_detail=True)) diff --git a/moto/greengrass/urls.py b/moto/greengrass/urls.py index 54cbb3ceb..6583c6aef 100644 --- a/moto/greengrass/urls.py +++ b/moto/greengrass/urls.py @@ -29,4 +29,8 @@ url_paths = { "{0}/greengrass/definition/subscriptions/(?P[^/]+)/versions$": response.subscription_definition_versions, "{0}/greengrass/definition/subscriptions/(?P[^/]+)/versions/(?P[^/]+)/?$": response.subscription_definition_version, "{0}/greengrass/definition/resources/(?P[^/]+)/versions/(?P[^/]+)/?$": response.resource_definition_version, + "{0}/greengrass/groups$": response.groups, + "{0}/greengrass/groups/(?P[^/]+)/?$": response.group, + "{0}/greengrass/groups/(?P[^/]+)/versions$": response.group_versions, + "{0}/greengrass/groups/(?P[^/]+)/versions/(?P[^/]+)/?$": response.group_version, } diff --git a/tests/test_greengrass/test_greengrass_groups.py b/tests/test_greengrass/test_greengrass_groups.py new file mode 100644 index 000000000..74a04eeef --- /dev/null +++ b/tests/test_greengrass/test_greengrass_groups.py @@ -0,0 +1,475 @@ +import boto3 +from botocore.exceptions import ClientError +import freezegun +import pytest + +from moto import mock_greengrass +from moto.core import get_account_id +from moto.settings import TEST_SERVER_MODE + +ACCOUNT_ID = get_account_id() + + +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_create_group(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + init_core_ver = { + "Cores": [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + } + + create_core_def_res = client.create_core_definition( + InitialVersion=init_core_ver, Name="TestCore" + ) + core_def_ver_arn = create_core_def_res["LatestVersionArn"] + + init_grp = {"CoreDefinitionVersionArn": core_def_ver_arn} + + grp_name = "TestGroup" + create_grp_res = client.create_group(Name=grp_name, InitialVersion=init_grp) + create_grp_res.should.have.key("Arn") + create_grp_res.should.have.key("Id") + create_grp_res.should.have.key("LastUpdatedTimestamp") + create_grp_res.should.have.key("LatestVersion") + create_grp_res.should.have.key("LatestVersionArn") + create_grp_res.should.have.key("Name").equals(grp_name) + create_grp_res["ResponseMetadata"]["HTTPStatusCode"].should.equal(201) + + if not TEST_SERVER_MODE: + create_grp_res.should.have.key("CreationTimestamp").equals( + "2022-06-01T12:00:00.000Z" + ) + create_grp_res.should.have.key("LastUpdatedTimestamp").equals( + "2022-06-01T12:00:00.000Z" + ) + + +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_list_groups(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + grp_name = "TestGroup" + create_grp_res = client.create_group(Name=grp_name) + group_id = create_grp_res["Id"] + + list_res = client.list_groups() + + group = list_res["Groups"][0] + group.should.have.key("Name").equals(grp_name) + group.should.have.key("Arn") + group.should.have.key("Id").equals(group_id) + group.should.have.key("LatestVersion") + group.should.have.key("LatestVersionArn") + if not TEST_SERVER_MODE: + group.should.have.key("CreationTimestamp").equal("2022-06-01T12:00:00.000Z") + group.should.have.key("LastUpdatedTimestamp").equals("2022-06-01T12:00:00.000Z") + + +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_get_group(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + grp_name = "TestGroup" + create_res = client.create_group(Name=grp_name) + group_id = create_res["Id"] + group_arn = create_res["Arn"] + latest_version = create_res["LatestVersion"] + latest_version_arn = create_res["LatestVersionArn"] + + get_res = client.get_group(GroupId=group_id) + + get_res.should.have.key("Name").equals(grp_name) + get_res.should.have.key("Arn").equals(group_arn) + get_res.should.have.key("Id").equals(group_id) + get_res.should.have.key("LatestVersion").equals(latest_version) + get_res.should.have.key("LatestVersionArn").equals(latest_version_arn) + get_res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + + if not TEST_SERVER_MODE: + get_res.should.have.key("CreationTimestamp").equal("2022-06-01T12:00:00.000Z") + get_res.should.have.key("LastUpdatedTimestamp").equals( + "2022-06-01T12:00:00.000Z" + ) + + +@mock_greengrass +def test_get_group_with_invalid_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + with pytest.raises(ClientError) as ex: + client.get_group(GroupId="b552443b-1888-469b-81f8-0ebc5ca92949") + ex.value.response["Error"]["Message"].should.equal( + "That Group Definition does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("IdNotFoundException") + + +@mock_greengrass +def test_delete_group(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_res = client.create_group(Name="TestGroup") + + group_id = create_res["Id"] + del_res = client.delete_group(GroupId=group_id) + del_res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + + +@mock_greengrass +def test_delete_group_with_invalid_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + + with pytest.raises(ClientError) as ex: + client.delete_group(GroupId="6fbffc21-989e-4d29-a793-a42f450a78c6") + ex.value.response["Error"]["Message"].should.equal( + "That group definition does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("IdNotFoundException") + + +@mock_greengrass +def test_update_group(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_res = client.create_group(Name="TestGroup") + + group_id = create_res["Id"] + updated_group_name = "UpdatedGroup" + update_res = client.update_group(GroupId=group_id, Name=updated_group_name) + update_res["ResponseMetadata"]["HTTPStatusCode"].should.equal(200) + + get_res = client.get_group(GroupId=group_id) + get_res.should.have.key("Name").equals(updated_group_name) + + +@mock_greengrass +def test_update_group_with_empty_name(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_res = client.create_group(Name="TestGroup") + + group_id = create_res["Id"] + + with pytest.raises(ClientError) as ex: + client.update_group(GroupId=group_id, Name="") + ex.value.response["Error"]["Message"].should.equal( + "Input does not contain any attributes to be updated" + ) + ex.value.response["Error"]["Code"].should.equal( + "InvalidContainerDefinitionException" + ) + + +@mock_greengrass +def test_update_group_with_invalid_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + + with pytest.raises(ClientError) as ex: + client.update_group(GroupId="6fbffc21-989e-4d29-a793-a42f450a78c6", Name="123") + ex.value.response["Error"]["Message"].should.equal( + "That group definition does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("IdNotFoundException") + + +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_create_group_version(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_grp_res = client.create_group(Name="TestGroup") + group_id = create_grp_res["Id"] + + group_ver_res = client.create_group_version(GroupId=group_id) + group_ver_res.should.have.key("Arn") + group_ver_res.should.have.key("CreationTimestamp") + group_ver_res.should.have.key("Id").equals(group_id) + group_ver_res.should.have.key("Version") + + if not TEST_SERVER_MODE: + group_ver_res["CreationTimestamp"].should.equal("2022-06-01T12:00:00.000Z") + + +@mock_greengrass +def test_create_group_version_with_invalid_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + with pytest.raises(ClientError) as ex: + client.create_group_version(GroupId="cd2ea6dc-6634-4e89-8441-8003500435f9") + + ex.value.response["Error"]["Message"].should.equal("That group does not exist.") + ex.value.response["Error"]["Code"].should.equal("IdNotFoundException") + + +@pytest.mark.parametrize( + "def_ver_key_name,arn,error_message", + [ + ( + "CoreDefinitionVersionArn", + "123", + "Cores definition reference does not exist", + ), + ( + "CoreDefinitionVersionArn", + "arn:aws:greengrass:ap-northeast-1:944137583148:/greengrass/definition/cores/fc3b3e5b-f1ce-4639-88d3-3ad897d95b2a/versions/dd0f800c-246c-4973-82cf-45b109cbd99b", + "Cores definition reference does not exist", + ), + ( + "DeviceDefinitionVersionArn", + "123", + "Devices definition reference does not exist", + ), + ( + "DeviceDefinitionVersionArn", + "arn:aws:greengrass:ap-northeast-1:944137583148:/greengrass/definition/devices/fc3b3e5b-f1ce-4639-88d3-3ad897d95b2a/versions/dd0f800c-246c-4973-82cf-45b109cbd99b", + "Devices definition reference does not exist", + ), + ( + "FunctionDefinitionVersionArn", + "123", + "Lambda definition reference does not exist", + ), + ( + "FunctionDefinitionVersionArn", + "arn:aws:greengrass:ap-northeast-1:944137583148:/greengrass/definition/functions/fc3b3e5b-f1ce-4639-88d3-3ad897d95b2a/versions/dd0f800c-246c-4973-82cf-45b109cbd99b", + "Lambda definition reference does not exist", + ), + ( + "ResourceDefinitionVersionArn", + "123", + "Resource definition reference does not exist", + ), + ( + "ResourceDefinitionVersionArn", + "arn:aws:greengrass:ap-northeast-1:944137583148:/greengrass/definition/resources/fc3b3e5b-f1ce-4639-88d3-3ad897d95b2a/versions/dd0f800c-246c-4973-82cf-45b109cbd99b", + "Resource definition reference does not exist", + ), + ( + "SubscriptionDefinitionVersionArn", + "123", + "Subscription definition reference does not exist", + ), + ( + "SubscriptionDefinitionVersionArn", + "arn:aws:greengrass:ap-northeast-1:944137583148:/greengrass/definition/subscriptions/fc3b3e5b-f1ce-4639-88d3-3ad897d95b2a/versions/dd0f800c-246c-4973-82cf-45b109cbd99b", + "Subscription definition reference does not exist", + ), + ], +) +@mock_greengrass +def test_create_group_version_with_invalid_version_arn( + def_ver_key_name, arn, error_message +): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_grp_res = client.create_group(Name="TestGroup") + group_id = create_grp_res["Id"] + + definition_versions = {def_ver_key_name: arn} + + with pytest.raises(ClientError) as ex: + client.create_group_version(GroupId=group_id, **definition_versions) + + ex.value.response["Error"]["Message"].should.equal( + f"The group is invalid or corrupted. (ErrorDetails: [{error_message}])" + ) + ex.value.response["Error"]["Code"].should.equal("400") + + +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_list_group_versions(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_res = client.create_group(Name="TestGroup") + group_id = create_res["Id"] + + group_vers_res = client.list_group_versions(GroupId=group_id) + + group_vers_res.should.have.key("Versions") + group_ver = group_vers_res["Versions"][0] + group_ver.should.have.key("Arn") + group_ver.should.have.key("CreationTimestamp") + group_ver.should.have.key("Id").equals(group_id) + group_ver.should.have.key("Version") + + if not TEST_SERVER_MODE: + group_ver["CreationTimestamp"].should.equal("2022-06-01T12:00:00.000Z") + + +@mock_greengrass +def test_list_group_versions_with_invalid_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + + with pytest.raises(ClientError) as ex: + client.list_group_versions(GroupId="7b0bdeae-54c7-47cf-9f93-561e672efd9c") + ex.value.response["Error"]["Message"].should.equal( + "That group definition does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("IdNotFoundException") + + +@freezegun.freeze_time("2022-06-01 12:00:00") +@mock_greengrass +def test_get_group_version(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + init_core_ver = { + "Cores": [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123456789", + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + } + + create_core_def_res = client.create_core_definition( + InitialVersion=init_core_ver, Name="TestCore" + ) + core_def_ver_arn = create_core_def_res["LatestVersionArn"] + + init_device_ver = { + "Devices": [ + { + "CertificateArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:cert/36ed61be9c6271ae8da174e29d0e033c06af149d7b21672f3800fe322044554d", + "Id": "123", + "SyncShadow": True, + "ThingArn": f"arn:aws:iot:ap-northeast-1:{ACCOUNT_ID}:thing/CoreThing", + } + ] + } + + create_device_res = client.create_device_definition( + InitialVersion=init_device_ver, Name="TestDevice" + ) + device_def_ver_arn = create_device_res["LatestVersionArn"] + + init_func_ver = { + "Functions": [ + { + "FunctionArn": "arn:aws:lambda:ap-northeast-1:123456789012:function:test-func:1", + "Id": "1234567890", + "FunctionConfiguration": { + "MemorySize": 16384, + "EncodingType": "binary", + "Pinned": True, + "Timeout": 3, + }, + } + ] + } + create_func_res = client.create_function_definition( + InitialVersion=init_func_ver, Name="TestFunc" + ) + func_def_ver_arn = create_func_res["LatestVersionArn"] + + init_resource_ver = { + "Resources": [ + { + "Id": "123", + "Name": "test_directory", + "ResourceDataContainer": { + "LocalVolumeResourceData": { + "DestinationPath": "/test_dir", + "GroupOwnerSetting": {"AutoAddGroupOwner": True}, + "SourcePath": "/home/ggc_user/test_dir", + } + }, + } + ] + } + + create_resource_res = client.create_resource_definition( + InitialVersion=init_resource_ver, Name="TestResource" + ) + resource_def_ver_arn = create_resource_res["LatestVersionArn"] + + init_subscription_ver = { + "Subscriptions": [ + { + "Id": "123456", + "Source": "arn:aws:lambda:ap-northeast-1:123456789012:function:test_func:1", + "Subject": "foo/bar", + "Target": "cloud", + } + ] + } + create_subscription_res = client.create_subscription_definition( + InitialVersion=init_subscription_ver, Name="TestSubscription" + ) + subscription_def_ver_arn = create_subscription_res["LatestVersionArn"] + + definition = { + "CoreDefinitionVersionArn": core_def_ver_arn, + "DeviceDefinitionVersionArn": device_def_ver_arn, + "FunctionDefinitionVersionArn": func_def_ver_arn, + "ResourceDefinitionVersionArn": resource_def_ver_arn, + "SubscriptionDefinitionVersionArn": subscription_def_ver_arn, + } + + grp_name = "TestGroup" + create_res = client.create_group(Name=grp_name, InitialVersion=definition) + group_id = create_res["Id"] + group_ver_id = create_res["LatestVersion"] + + group_ver_res = client.get_group_version( + GroupId=group_id, GroupVersionId=group_ver_id + ) + + group_ver_res.should.have.key("Arn") + group_ver_res.should.have.key("CreationTimestamp") + group_ver_res.should.have.key("Definition").should.equal(definition) + group_ver_res.should.have.key("Id").equals(group_id) + group_ver_res.should.have.key("Version").should.equal(group_ver_id) + + if not TEST_SERVER_MODE: + group_ver_res["CreationTimestamp"].should.equal("2022-06-01T12:00:00.000Z") + + +@mock_greengrass +def test_get_group_version_with_invalid_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + + with pytest.raises(ClientError) as ex: + client.get_group_version( + GroupId="7b0bdeae-54c7-47cf-9f93-561e672efd9c", + GroupVersionId="7b0bdeae-54c7-47cf-9f93-561e672efd9c", + ) + ex.value.response["Error"]["Message"].should.equal( + "That group definition does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("IdNotFoundException") + + +@mock_greengrass +def test_get_group_version_with_invalid_version_id(): + + client = boto3.client("greengrass", region_name="ap-northeast-1") + create_res = client.create_group(Name="TestGroup") + + group_id = create_res["Id"] + invalid_group_ver_id = "7b0bdeae-54c7-47cf-9f93-561e672efd9c" + + with pytest.raises(ClientError) as ex: + client.get_group_version( + GroupId=group_id, + GroupVersionId=invalid_group_ver_id, + ) + ex.value.response["Error"]["Message"].should.equal( + f"Version {invalid_group_ver_id} of Group Definition {group_id} does not exist." + ) + ex.value.response["Error"]["Code"].should.equal("VersionNotFoundException")