diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 4a5561038..b113eddf4 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -4040,6 +4040,127 @@
- [ ] synthesize_speech
+## quicksight
+
+6% implemented
+
+- [ ] cancel_ingestion
+- [ ] create_account_customization
+- [ ] create_analysis
+- [ ] create_dashboard
+- [ ] create_data_set
+- [ ] create_data_source
+- [ ] create_folder
+- [ ] create_folder_membership
+- [X] create_group
+- [ ] create_group_membership
+- [ ] create_iam_policy_assignment
+- [ ] create_ingestion
+- [ ] create_namespace
+- [ ] create_template
+- [ ] create_template_alias
+- [ ] create_theme
+- [ ] create_theme_alias
+- [ ] delete_account_customization
+- [ ] delete_analysis
+- [ ] delete_dashboard
+- [ ] delete_data_set
+- [ ] delete_data_source
+- [ ] delete_folder
+- [ ] delete_folder_membership
+- [X] delete_group
+- [ ] delete_group_membership
+- [ ] delete_iam_policy_assignment
+- [ ] delete_namespace
+- [ ] delete_template
+- [ ] delete_template_alias
+- [ ] delete_theme
+- [ ] delete_theme_alias
+- [X] delete_user
+- [ ] delete_user_by_principal_id
+- [ ] describe_account_customization
+- [ ] describe_account_settings
+- [ ] describe_analysis
+- [ ] describe_analysis_permissions
+- [ ] describe_dashboard
+- [ ] describe_dashboard_permissions
+- [ ] describe_data_set
+- [ ] describe_data_set_permissions
+- [ ] describe_data_source
+- [ ] describe_data_source_permissions
+- [ ] describe_folder
+- [ ] describe_folder_permissions
+- [ ] describe_folder_resolved_permissions
+- [X] describe_group
+- [ ] describe_iam_policy_assignment
+- [ ] describe_ingestion
+- [ ] describe_ip_restriction
+- [ ] describe_namespace
+- [ ] describe_template
+- [ ] describe_template_alias
+- [ ] describe_template_permissions
+- [ ] describe_theme
+- [ ] describe_theme_alias
+- [ ] describe_theme_permissions
+- [X] describe_user
+- [ ] generate_embed_url_for_anonymous_user
+- [ ] generate_embed_url_for_registered_user
+- [ ] get_dashboard_embed_url
+- [ ] get_session_embed_url
+- [ ] list_analyses
+- [ ] list_dashboard_versions
+- [ ] list_dashboards
+- [ ] list_data_sets
+- [ ] list_data_sources
+- [ ] list_folder_members
+- [ ] list_folders
+- [ ] list_group_memberships
+- [ ] list_groups
+- [ ] list_iam_policy_assignments
+- [ ] list_iam_policy_assignments_for_user
+- [ ] list_ingestions
+- [ ] list_namespaces
+- [ ] list_tags_for_resource
+- [ ] list_template_aliases
+- [ ] list_template_versions
+- [ ] list_templates
+- [ ] list_theme_aliases
+- [ ] list_theme_versions
+- [ ] list_themes
+- [ ] list_user_groups
+- [ ] list_users
+- [X] register_user
+- [ ] restore_analysis
+- [ ] search_analyses
+- [ ] search_dashboards
+- [ ] search_folders
+- [ ] tag_resource
+- [ ] untag_resource
+- [ ] update_account_customization
+- [ ] update_account_settings
+- [ ] update_analysis
+- [ ] update_analysis_permissions
+- [ ] update_dashboard
+- [ ] update_dashboard_permissions
+- [ ] update_dashboard_published_version
+- [ ] update_data_set
+- [ ] update_data_set_permissions
+- [ ] update_data_source
+- [ ] update_data_source_permissions
+- [ ] update_folder
+- [ ] update_folder_permissions
+- [X] update_group
+- [ ] update_iam_policy_assignment
+- [ ] update_ip_restriction
+- [ ] update_template
+- [ ] update_template_alias
+- [ ] update_template_permissions
+- [ ] update_theme
+- [ ] update_theme_alias
+- [ ] update_theme_permissions
+- [ ] update_user
+
+
## ram
20% implemented
@@ -5762,7 +5883,6 @@
- proton
- qldb
- qldb-session
-- quicksight
- rbin
- rds-data
- rekognition
diff --git a/docs/docs/services/quicksight.rst b/docs/docs/services/quicksight.rst
new file mode 100644
index 000000000..0732a9043
--- /dev/null
+++ b/docs/docs/services/quicksight.rst
@@ -0,0 +1,150 @@
+.. _implementedservice_quicksight:
+
+.. |start-h3| raw:: html
+
+
+
+.. |end-h3| raw:: html
+
+
+
+==========
+quicksight
+==========
+
+.. autoclass:: moto.quicksight.models.QuickSightBackend
+
+|start-h3| Example usage |end-h3|
+
+.. sourcecode:: python
+
+ @mock_quicksight
+ def test_quicksight_behaviour:
+ boto3.client("quicksight")
+ ...
+
+
+
+|start-h3| Implemented features for this service |end-h3|
+
+- [ ] cancel_ingestion
+- [ ] create_account_customization
+- [ ] create_analysis
+- [ ] create_dashboard
+- [ ] create_data_set
+- [ ] create_data_source
+- [ ] create_folder
+- [ ] create_folder_membership
+- [X] create_group
+- [ ] create_group_membership
+- [ ] create_iam_policy_assignment
+- [ ] create_ingestion
+- [ ] create_namespace
+- [ ] create_template
+- [ ] create_template_alias
+- [ ] create_theme
+- [ ] create_theme_alias
+- [ ] delete_account_customization
+- [ ] delete_analysis
+- [ ] delete_dashboard
+- [ ] delete_data_set
+- [ ] delete_data_source
+- [ ] delete_folder
+- [ ] delete_folder_membership
+- [X] delete_group
+- [ ] delete_group_membership
+- [ ] delete_iam_policy_assignment
+- [ ] delete_namespace
+- [ ] delete_template
+- [ ] delete_template_alias
+- [ ] delete_theme
+- [ ] delete_theme_alias
+- [X] delete_user
+- [ ] delete_user_by_principal_id
+- [ ] describe_account_customization
+- [ ] describe_account_settings
+- [ ] describe_analysis
+- [ ] describe_analysis_permissions
+- [ ] describe_dashboard
+- [ ] describe_dashboard_permissions
+- [ ] describe_data_set
+- [ ] describe_data_set_permissions
+- [ ] describe_data_source
+- [ ] describe_data_source_permissions
+- [ ] describe_folder
+- [ ] describe_folder_permissions
+- [ ] describe_folder_resolved_permissions
+- [X] describe_group
+- [ ] describe_iam_policy_assignment
+- [ ] describe_ingestion
+- [ ] describe_ip_restriction
+- [ ] describe_namespace
+- [ ] describe_template
+- [ ] describe_template_alias
+- [ ] describe_template_permissions
+- [ ] describe_theme
+- [ ] describe_theme_alias
+- [ ] describe_theme_permissions
+- [X] describe_user
+- [ ] generate_embed_url_for_anonymous_user
+- [ ] generate_embed_url_for_registered_user
+- [ ] get_dashboard_embed_url
+- [ ] get_session_embed_url
+- [ ] list_analyses
+- [ ] list_dashboard_versions
+- [ ] list_dashboards
+- [ ] list_data_sets
+- [ ] list_data_sources
+- [ ] list_folder_members
+- [ ] list_folders
+- [ ] list_group_memberships
+- [ ] list_groups
+- [ ] list_iam_policy_assignments
+- [ ] list_iam_policy_assignments_for_user
+- [ ] list_ingestions
+- [ ] list_namespaces
+- [ ] list_tags_for_resource
+- [ ] list_template_aliases
+- [ ] list_template_versions
+- [ ] list_templates
+- [ ] list_theme_aliases
+- [ ] list_theme_versions
+- [ ] list_themes
+- [ ] list_user_groups
+- [ ] list_users
+- [X] register_user
+
+ The following parameters are not yet implemented:
+ IamArn, SessionName, CustomsPermissionsName, ExternalLoginFederationProviderType, CustomFederationProviderUrl, ExternalLoginId
+
+
+- [ ] restore_analysis
+- [ ] search_analyses
+- [ ] search_dashboards
+- [ ] search_folders
+- [ ] tag_resource
+- [ ] untag_resource
+- [ ] update_account_customization
+- [ ] update_account_settings
+- [ ] update_analysis
+- [ ] update_analysis_permissions
+- [ ] update_dashboard
+- [ ] update_dashboard_permissions
+- [ ] update_dashboard_published_version
+- [ ] update_data_set
+- [ ] update_data_set_permissions
+- [ ] update_data_source
+- [ ] update_data_source_permissions
+- [ ] update_folder
+- [ ] update_folder_permissions
+- [X] update_group
+- [ ] update_iam_policy_assignment
+- [ ] update_ip_restriction
+- [ ] update_template
+- [ ] update_template_alias
+- [ ] update_template_permissions
+- [ ] update_theme
+- [ ] update_theme_alias
+- [ ] update_theme_permissions
+- [ ] update_user
+
diff --git a/moto/__init__.py b/moto/__init__.py
index 92658a359..75796f5da 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -124,6 +124,7 @@ mock_opsworks = lazy_load(".opsworks", "mock_opsworks")
mock_organizations = lazy_load(".organizations", "mock_organizations")
mock_pinpoint = lazy_load(".pinpoint", "mock_pinpoint")
mock_polly = lazy_load(".polly", "mock_polly")
+mock_quicksight = lazy_load(".quicksight", "mock_quicksight")
mock_ram = lazy_load(".ram", "mock_ram")
mock_rds = lazy_load(".rds", "mock_rds")
mock_rds2 = lazy_load(".rds", "mock_rds", use_instead=("mock_rds2", "mock_rds"))
diff --git a/moto/backend_index.py b/moto/backend_index.py
index 167d960f5..64808c91c 100644
--- a/moto/backend_index.py
+++ b/moto/backend_index.py
@@ -1,4 +1,4 @@
-# autogenerated by ./scripts/update_backend_index.py
+# autogenerated by scripts/update_backend_index.py
import re
backend_url_patterns = [
@@ -32,7 +32,6 @@ backend_url_patterns = [
("dms", re.compile("https?://dms\\.(.+)\\.amazonaws\\.com")),
("ds", re.compile("https?://ds\\.(.+)\\.amazonaws\\.com")),
("dynamodb", re.compile("https?://dynamodb\\.(.+)\\.amazonaws\\.com")),
- ("dynamodb2", re.compile("https?://dynamodb\\.(.+)\\.amazonaws\\.com")),
(
"dynamodbstreams",
re.compile("https?://streams\\.dynamodb\\.(.+)\\.amazonaws.com"),
@@ -97,6 +96,7 @@ backend_url_patterns = [
("organizations", re.compile("https?://organizations\\.(.+)\\.amazonaws\\.com")),
("pinpoint", re.compile("https?://pinpoint\\.(.+)\\.amazonaws\\.com")),
("polly", re.compile("https?://polly\\.(.+)\\.amazonaws.com")),
+ ("quicksight", re.compile("https?://quicksight\\.(.+)\\.amazonaws\\.com")),
("ram", re.compile("https?://ram\\.(.+)\\.amazonaws.com")),
("rds", re.compile("https?://rds\\.(.+)\\.amazonaws\\.com")),
("rds", re.compile("https?://rds\\.amazonaws\\.com")),
@@ -154,4 +154,5 @@ backend_url_patterns = [
("transcribe", re.compile("https?://transcribe\\.(.+)\\.amazonaws\\.com")),
("wafv2", re.compile("https?://wafv2\\.(.+)\\.amazonaws.com")),
("xray", re.compile("https?://xray\\.(.+)\\.amazonaws.com")),
+ ("dynamodb_v20111205", re.compile("https?://dynamodb\\.(.+)\\.amazonaws\\.com")),
]
diff --git a/moto/quicksight/__init__.py b/moto/quicksight/__init__.py
new file mode 100644
index 000000000..c58340038
--- /dev/null
+++ b/moto/quicksight/__init__.py
@@ -0,0 +1,5 @@
+"""quicksight module initialization; sets value for base decorator."""
+from .models import quicksight_backends
+from ..core.models import base_decorator
+
+mock_quicksight = base_decorator(quicksight_backends)
diff --git a/moto/quicksight/exceptions.py b/moto/quicksight/exceptions.py
new file mode 100644
index 000000000..045cf4c7e
--- /dev/null
+++ b/moto/quicksight/exceptions.py
@@ -0,0 +1,7 @@
+"""Exceptions raised by the quicksight service."""
+from moto.core.exceptions import JsonRESTError
+
+
+class ResourceNotFoundException(JsonRESTError):
+ def __init__(self, msg):
+ super().__init__("ResourceNotFoundException", msg)
diff --git a/moto/quicksight/models.py b/moto/quicksight/models.py
new file mode 100644
index 000000000..ce75d8a85
--- /dev/null
+++ b/moto/quicksight/models.py
@@ -0,0 +1,112 @@
+"""QuickSightBackend class with methods for supported APIs."""
+
+from moto.core import ACCOUNT_ID, BaseBackend, BaseModel
+from moto.core.utils import BackendDict
+from .exceptions import ResourceNotFoundException
+
+
+class QuicksightGroup(BaseModel):
+ def __init__(self, region, group_name, description, aws_account_id, namespace):
+ self.arn = (
+ f"arn:aws:quicksight:{region}:{ACCOUNT_ID}:group/default/{group_name}"
+ )
+ self.group_name = group_name
+ self.description = description
+ self.aws_account_id = aws_account_id
+ self.namespace = namespace
+
+ def to_json(self):
+ return {
+ "Arn": self.arn,
+ "GroupName": self.group_name,
+ "Description": self.description,
+ "PrincipalId": self.aws_account_id,
+ "Namespace": self.namespace,
+ }
+
+
+class QuicksightUser(BaseModel):
+ def __init__(self, region, email, identity_type, username, user_role):
+ self.arn = f"arn:aws:quicksight:{region}:{ACCOUNT_ID}:user/default/{username}"
+ self.email = email
+ self.identity_type = identity_type
+ self.username = username
+ self.user_role = user_role
+ self.active = False
+
+ def to_json(self):
+ return {
+ "Arn": self.arn,
+ "Email": self.email,
+ "IdentityType": self.identity_type,
+ "Role": self.user_role,
+ "UserName": self.username,
+ "Active": self.active,
+ }
+
+
+class QuickSightBackend(BaseBackend):
+ """Implementation of QuickSight APIs."""
+
+ def __init__(self, region_name=None):
+ self.region_name = region_name
+ self.groups = dict()
+ self.users = dict()
+
+ def reset(self):
+ """Re-initialize all attributes for this instance."""
+ region_name = self.region_name
+ self.__dict__ = {}
+ self.__init__(region_name)
+
+ def create_group(self, group_name, description, aws_account_id, namespace):
+ group = QuicksightGroup(
+ region=self.region_name,
+ group_name=group_name,
+ description=description,
+ aws_account_id=aws_account_id,
+ namespace=namespace,
+ )
+ self.groups[f"{aws_account_id}:{namespace}:{group_name}"] = group
+ return group
+
+ def delete_group(self, aws_account_id, namespace, group_name):
+ self.groups.pop(f"{aws_account_id}:{namespace}:{group_name}", None)
+
+ def delete_user(self, aws_account_id, namespace, user_name):
+ self.users.pop(f"{aws_account_id}:{namespace}:{user_name}", None)
+
+ def describe_group(self, aws_account_id, namespace, group_name):
+ if f"{aws_account_id}:{namespace}:{group_name}" not in self.groups:
+ raise ResourceNotFoundException(f"Group {group_name} not found")
+ return self.groups[f"{aws_account_id}:{namespace}:{group_name}"]
+
+ def describe_user(self, aws_account_id, namespace, user_name):
+ if f"{aws_account_id}:{namespace}:{user_name}" not in self.users:
+ raise ResourceNotFoundException(f"User {user_name} not found")
+ return self.users[f"{aws_account_id}:{namespace}:{user_name}"]
+
+ def register_user(
+ self, identity_type, email, user_role, aws_account_id, namespace, user_name
+ ):
+ """
+ The following parameters are not yet implemented:
+ IamArn, SessionName, CustomsPermissionsName, ExternalLoginFederationProviderType, CustomFederationProviderUrl, ExternalLoginId
+ """
+ user = QuicksightUser(
+ region=self.region_name,
+ email=email,
+ identity_type=identity_type,
+ user_role=user_role,
+ username=user_name,
+ )
+ self.users[f"{aws_account_id}:{namespace}:{user_name}"] = user
+ return user
+
+ def update_group(self, aws_account_id, namespace, group_name, description):
+ group = self.describe_group(aws_account_id, namespace, group_name)
+ group.description = description
+ return group
+
+
+quicksight_backends = BackendDict(QuickSightBackend, "quicksight")
diff --git a/moto/quicksight/responses.py b/moto/quicksight/responses.py
new file mode 100644
index 000000000..69a45f446
--- /dev/null
+++ b/moto/quicksight/responses.py
@@ -0,0 +1,119 @@
+"""Handles incoming quicksight requests, invokes methods, returns responses."""
+import json
+
+from moto.core.responses import BaseResponse
+from .models import quicksight_backends
+
+
+class QuickSightResponse(BaseResponse):
+ """Handler for QuickSight requests and responses."""
+
+ @property
+ def quicksight_backend(self):
+ """Return backend instance specific for this region."""
+ return quicksight_backends[self.region]
+
+ def groups(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+ if request.method == "POST":
+ return self.create_group()
+
+ def group(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+ if request.method == "GET":
+ return self.describe_group()
+ if request.method == "DELETE":
+ return self.delete_group()
+ if request.method == "PUT":
+ return self.update_group()
+
+ def users(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+ if request.method == "POST":
+ return self.register_user()
+
+ def user(self, request, full_url, headers):
+ self.setup_class(request, full_url, headers)
+ if request.method == "GET":
+ return self.describe_user()
+ if request.method == "DELETE":
+ return self.delete_user()
+
+ def create_group(self):
+ params = json.loads(self.body)
+ group_name = params.get("GroupName")
+ description = params.get("Description")
+ aws_account_id = self.path.split("/")[-4]
+ namespace = self.path.split("/")[-2]
+ group = self.quicksight_backend.create_group(
+ group_name=group_name,
+ description=description,
+ aws_account_id=aws_account_id,
+ namespace=namespace,
+ )
+ return 200, {}, json.dumps(dict(Group=group.to_json()))
+
+ def register_user(self):
+ params = json.loads(self.body)
+ identity_type = params.get("IdentityType")
+ email = params.get("Email")
+ user_role = params.get("UserRole")
+ aws_account_id = self.path.split("/")[-4]
+ namespace = self.path.split("/")[-2]
+ user_name = params.get("UserName")
+ user = self.quicksight_backend.register_user(
+ identity_type=identity_type,
+ email=email,
+ user_role=user_role,
+ aws_account_id=aws_account_id,
+ namespace=namespace,
+ user_name=user_name,
+ )
+ return 200, {}, json.dumps(dict(User=user.to_json(), UserInvitationUrl="TBD"))
+
+ def describe_group(self):
+ aws_account_id = self.path.split("/")[-5]
+ namespace = self.path.split("/")[-3]
+ group_name = self.path.split("/")[-1]
+
+ group = self.quicksight_backend.describe_group(
+ aws_account_id, namespace, group_name
+ )
+ return 200, {}, json.dumps(dict(Group=group.to_json()))
+
+ def describe_user(self):
+ aws_account_id = self.path.split("/")[-5]
+ namespace = self.path.split("/")[-3]
+ user_name = self.path.split("/")[-1]
+
+ user = self.quicksight_backend.describe_user(
+ aws_account_id, namespace, user_name
+ )
+ return 200, {}, json.dumps(dict(User=user.to_json()))
+
+ def delete_group(self):
+ aws_account_id = self.path.split("/")[-5]
+ namespace = self.path.split("/")[-3]
+ group_name = self.path.split("/")[-1]
+
+ self.quicksight_backend.delete_group(aws_account_id, namespace, group_name)
+ return 204, {}, json.dumps({"Status": 204})
+
+ def delete_user(self):
+ aws_account_id = self.path.split("/")[-5]
+ namespace = self.path.split("/")[-3]
+ user_name = self.path.split("/")[-1]
+
+ self.quicksight_backend.delete_user(aws_account_id, namespace, user_name)
+ return 204, {}, json.dumps({"Status": 204})
+
+ def update_group(self):
+ aws_account_id = self.path.split("/")[-5]
+ namespace = self.path.split("/")[-3]
+ group_name = self.path.split("/")[-1]
+ description = json.loads(self.body).get("Description")
+
+ group = self.quicksight_backend.update_group(
+ aws_account_id, namespace, group_name, description
+ )
+ return 200, {}, json.dumps(dict(Group=group.to_json()))
diff --git a/moto/quicksight/urls.py b/moto/quicksight/urls.py
new file mode 100644
index 000000000..872ed93cf
--- /dev/null
+++ b/moto/quicksight/urls.py
@@ -0,0 +1,17 @@
+"""quicksight base URL and path."""
+from .responses import QuickSightResponse
+
+url_bases = [
+ r"https?://quicksight\.(.+)\.amazonaws\.com",
+]
+
+
+response = QuickSightResponse()
+
+
+url_paths = {
+ r"{0}/accounts/(?P[\d]+)/namespaces/(?P[^/.]+)/groups$": response.groups,
+ r"{0}/accounts/(?P[\d]+)/namespaces/(?P[^/.]+)/groups/(?P[^/.]+)$": response.group,
+ r"{0}/accounts/(?P[\d]+)/namespaces/(?P[^/.]+)/users$": response.users,
+ r"{0}/accounts/(?P[\d]+)/namespaces/(?P[^/.]+)/users/(?P[^/.]+)$": response.user,
+}
diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt
index d51728bba..2d6e1be66 100644
--- a/tests/terraform-tests.success.txt
+++ b/tests/terraform-tests.success.txt
@@ -113,6 +113,8 @@ TestAccAWSPartition
TestAccAWSPinpointApp
TestAccAWSPinpointEventStream
TestAccAWSProvider
+TestAccAWSQuickSightGroup
+TestAccAWSQuickSightUser
TestAccAWSRedshiftServiceAccount
TestAccAWSRolePolicyAttachment
TestAccAWSRouteTable_
diff --git a/tests/test_quicksight/__init__.py b/tests/test_quicksight/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_quicksight/test_quicksight_groups.py b/tests/test_quicksight/test_quicksight_groups.py
new file mode 100644
index 000000000..89bcbeb4c
--- /dev/null
+++ b/tests/test_quicksight/test_quicksight_groups.py
@@ -0,0 +1,108 @@
+"""Unit tests for quicksight-supported APIs."""
+import boto3
+import pytest
+import sure # noqa # pylint: disable=unused-import
+
+from botocore.exceptions import ClientError
+from moto import mock_quicksight
+from moto.core import ACCOUNT_ID
+
+# See our Development Tips on writing tests for hints on how to write good tests:
+# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
+
+
+@mock_quicksight
+def test_create_group():
+ client = boto3.client("quicksight", region_name="us-west-2")
+ resp = client.create_group(
+ AwsAccountId=ACCOUNT_ID,
+ Namespace="default",
+ GroupName="mygroup",
+ Description="my new fancy group",
+ )
+
+ resp.should.have.key("Group")
+
+ resp["Group"].should.have.key("Arn").equals(
+ f"arn:aws:quicksight:us-west-2:{ACCOUNT_ID}:group/default/mygroup"
+ )
+ resp["Group"].should.have.key("GroupName").equals("mygroup")
+ resp["Group"].should.have.key("Description").equals("my new fancy group")
+ resp["Group"].should.have.key("PrincipalId").equals(f"{ACCOUNT_ID}")
+
+
+@mock_quicksight
+def test_describe_group():
+ client = boto3.client("quicksight", region_name="us-west-2")
+ client.create_group(
+ AwsAccountId=ACCOUNT_ID,
+ Namespace="default",
+ GroupName="mygroup",
+ Description="my new fancy group",
+ )
+
+ resp = client.describe_group(
+ GroupName="mygroup", AwsAccountId=ACCOUNT_ID, Namespace="default"
+ )
+
+ resp.should.have.key("Group")
+
+ resp["Group"].should.have.key("Arn").equals(
+ f"arn:aws:quicksight:us-west-2:{ACCOUNT_ID}:group/default/mygroup"
+ )
+ resp["Group"].should.have.key("GroupName").equals("mygroup")
+ resp["Group"].should.have.key("Description").equals("my new fancy group")
+ resp["Group"].should.have.key("PrincipalId").equals(f"{ACCOUNT_ID}")
+
+
+@mock_quicksight
+def test_update_group():
+ client = boto3.client("quicksight", region_name="us-west-2")
+ client.create_group(
+ AwsAccountId=ACCOUNT_ID,
+ Namespace="default",
+ GroupName="mygroup",
+ Description="desc1",
+ )
+
+ resp = client.update_group(
+ GroupName="mygroup",
+ AwsAccountId=ACCOUNT_ID,
+ Namespace="default",
+ Description="desc2",
+ )
+ resp.should.have.key("Group").should.have.key("Description").equals("desc2")
+
+ resp = client.describe_group(
+ GroupName="mygroup", AwsAccountId=ACCOUNT_ID, Namespace="default"
+ )
+
+ resp.should.have.key("Group")
+ resp["Group"].should.have.key("Arn").equals(
+ f"arn:aws:quicksight:us-west-2:{ACCOUNT_ID}:group/default/mygroup"
+ )
+ resp["Group"].should.have.key("GroupName").equals("mygroup")
+ resp["Group"].should.have.key("Description").equals("desc2")
+ resp["Group"].should.have.key("PrincipalId").equals(f"{ACCOUNT_ID}")
+
+
+@mock_quicksight
+def test_delete_group():
+ client = boto3.client("quicksight", region_name="us-east-2")
+ client.create_group(
+ AwsAccountId=ACCOUNT_ID,
+ Namespace="default",
+ GroupName="mygroup",
+ Description="my new fancy group",
+ )
+
+ client.delete_group(
+ GroupName="mygroup", AwsAccountId=ACCOUNT_ID, Namespace="default"
+ )
+
+ with pytest.raises(ClientError) as exc:
+ client.describe_group(
+ GroupName="mygroup", AwsAccountId=ACCOUNT_ID, Namespace="default"
+ )
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("ResourceNotFoundException")
diff --git a/tests/test_quicksight/test_quicksight_users.py b/tests/test_quicksight/test_quicksight_users.py
new file mode 100644
index 000000000..238861f53
--- /dev/null
+++ b/tests/test_quicksight/test_quicksight_users.py
@@ -0,0 +1,88 @@
+"""Unit tests for quicksight-supported APIs."""
+import boto3
+import pytest
+import sure # noqa # pylint: disable=unused-import
+
+from botocore.exceptions import ClientError
+from moto import mock_quicksight
+from moto.core import ACCOUNT_ID
+
+# See our Development Tips on writing tests for hints on how to write good tests:
+# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
+
+
+@mock_quicksight
+def test_register_user__quicksight():
+ client = boto3.client("quicksight", region_name="us-east-2")
+ resp = client.register_user(
+ AwsAccountId=ACCOUNT_ID,
+ Namespace="default",
+ Email="fakeemail@example.com",
+ IdentityType="QUICKSIGHT",
+ UserName="tfacctestm9hpsr970z",
+ UserRole="READER",
+ )
+
+ resp.should.have.key("UserInvitationUrl")
+ resp.should.have.key("User")
+
+ resp["User"].should.have.key("Arn").equals(
+ f"arn:aws:quicksight:us-east-2:{ACCOUNT_ID}:user/default/tfacctestm9hpsr970z"
+ )
+ resp["User"].should.have.key("UserName").equals("tfacctestm9hpsr970z")
+ resp["User"].should.have.key("Email").equals("fakeemail@example.com")
+ resp["User"].should.have.key("Role").equals("READER")
+ resp["User"].should.have.key("IdentityType").equals("QUICKSIGHT")
+ resp["User"].should.have.key("Active").equals(False)
+
+
+@mock_quicksight
+def test_describe_user__quicksight():
+ client = boto3.client("quicksight", region_name="us-east-1")
+ client.register_user(
+ AwsAccountId=ACCOUNT_ID,
+ Namespace="default",
+ Email="fakeemail@example.com",
+ IdentityType="QUICKSIGHT",
+ UserName="tfacctestm9hpsr970z",
+ UserRole="READER",
+ )
+
+ resp = client.describe_user(
+ UserName="tfacctestm9hpsr970z", AwsAccountId=ACCOUNT_ID, Namespace="default"
+ )
+
+ resp.should.have.key("User")
+
+ resp["User"].should.have.key("Arn").equals(
+ f"arn:aws:quicksight:us-east-1:{ACCOUNT_ID}:user/default/tfacctestm9hpsr970z"
+ )
+ resp["User"].should.have.key("UserName").equals("tfacctestm9hpsr970z")
+ resp["User"].should.have.key("Email").equals("fakeemail@example.com")
+ resp["User"].should.have.key("Role").equals("READER")
+ resp["User"].should.have.key("IdentityType").equals("QUICKSIGHT")
+ resp["User"].should.have.key("Active").equals(False)
+
+
+@mock_quicksight
+def test_delete_user__quicksight():
+ client = boto3.client("quicksight", region_name="us-east-2")
+ client.register_user(
+ AwsAccountId=ACCOUNT_ID,
+ Namespace="default",
+ Email="fakeemail@example.com",
+ IdentityType="QUICKSIGHT",
+ UserName="tfacctestm9hpsr970z",
+ UserRole="READER",
+ )
+
+ client.delete_user(
+ UserName="tfacctestm9hpsr970z", AwsAccountId=ACCOUNT_ID, Namespace="default"
+ )
+
+ with pytest.raises(ClientError) as exc:
+ client.describe_user(
+ UserName="tfacctestm9hpsr970z", AwsAccountId=ACCOUNT_ID, Namespace="default"
+ )
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("ResourceNotFoundException")