diff --git a/moto/organizations/models.py b/moto/organizations/models.py index 11aabae16..e25342f04 100644 --- a/moto/organizations/models.py +++ b/moto/organizations/models.py @@ -20,6 +20,8 @@ from moto.organizations.exceptions import ( PolicyTypeNotEnabledException, TargetNotFoundException, ) +from moto.utilities.paginator import paginate +from .utils import PAGINATION_MODEL class FakeOrganization(BaseModel): @@ -495,8 +497,11 @@ class OrganizationsBackend(BaseBackend): next_token = str(len(accounts_resp)) return dict(CreateAccountStatuses=accounts_resp, NextToken=next_token) + @paginate(pagination_model=PAGINATION_MODEL) def list_accounts(self): - return dict(Accounts=[account.describe() for account in self.accounts]) + accounts = [account.describe() for account in self.accounts] + accounts = sorted(accounts, key=lambda x: x["JoinedTimestamp"]) + return accounts def list_accounts_for_parent(self, **kwargs): parent_id = self.validate_parent_id(kwargs["ParentId"]) diff --git a/moto/organizations/responses.py b/moto/organizations/responses.py index 8734ab10f..5d0bdc482 100644 --- a/moto/organizations/responses.py +++ b/moto/organizations/responses.py @@ -85,7 +85,15 @@ class OrganizationsResponse(BaseResponse): ) def list_accounts(self): - return json.dumps(self.organizations_backend.list_accounts()) + max_results = self._get_int_param("MaxResults") + next_token = self._get_param("NextToken") + accounts, next_token = self.organizations_backend.list_accounts( + max_results=max_results, next_token=next_token + ) + response = {"Accounts": accounts} + if next_token: + response["NextToken"] = next_token + return json.dumps(response) def list_accounts_for_parent(self): return json.dumps( diff --git a/moto/organizations/utils.py b/moto/organizations/utils.py index f0821b629..c5ff3f0b4 100644 --- a/moto/organizations/utils.py +++ b/moto/organizations/utils.py @@ -33,6 +33,16 @@ ACCOUNT_ID_REGEX = r"[0-9]{%s}" % ACCOUNT_ID_SIZE CREATE_ACCOUNT_STATUS_ID_REGEX = r"car-[a-z0-9]{%s}" % CREATE_ACCOUNT_STATUS_ID_SIZE POLICY_ID_REGEX = r"%s|p-[a-z0-9]{%s}" % (DEFAULT_POLICY_ID, POLICY_ID_SIZE) +PAGINATION_MODEL = { + "list_accounts": { + "input_token": "next_token", + "limit_key": "max_results", + "limit_default": 100, + "result_key": "Accounts", + "unique_attribute": "JoinedTimestamp", + }, +} + def make_random_org_id(): # The regex pattern for an organization ID string requires "o-" diff --git a/moto/utilities/paginator.py b/moto/utilities/paginator.py index 2a57ab3fa..bbcb2dbe7 100644 --- a/moto/utilities/paginator.py +++ b/moto/utilities/paginator.py @@ -137,7 +137,7 @@ class Paginator(object): predicate_values = unique_attributes.split("|") for (index, attr) in enumerate(self._unique_attributes): curr_val = item[attr] if type(item) == dict else getattr(item, attr, None) - if not curr_val == predicate_values[index]: + if not str(curr_val) == predicate_values[index]: return False return True @@ -148,9 +148,9 @@ class Paginator(object): range_keys = [] for attr in self._unique_attributes: if type(next_item) == dict: - range_keys.append(next_item[attr]) + range_keys.append(str(next_item[attr])) else: - range_keys.append(getattr(next_item, attr)) + range_keys.append(str(getattr(next_item, attr))) token_dict["uniqueAttributes"] = "|".join(range_keys) return self._token_encoder.encode(token_dict) diff --git a/tests/test_organizations/test_organizations_boto3.py b/tests/test_organizations/test_organizations_boto3.py index 8b00dfead..66436a930 100644 --- a/tests/test_organizations/test_organizations_boto3.py +++ b/tests/test_organizations/test_organizations_boto3.py @@ -229,6 +229,25 @@ def test_list_accounts(): accounts[3]["Email"].should.equal(mockname + "2" + "@" + mockdomain) +@mock_organizations +def test_list_accounts_pagination(): + client = boto3.client("organizations", region_name="us-east-1") + client.create_organization(FeatureSet="ALL") + for i in range(25): + name = mockname + str(i) + email = name + "@" + mockdomain + client.create_account(AccountName=name, Email=email) + response = client.list_accounts() + response.should_not.have.key("NextToken") + len(response["Accounts"]).should.be.greater_than_or_equal_to(i) + + paginator = client.get_paginator("list_accounts") + page_iterator = paginator.paginate(MaxResults=5) + for page in page_iterator: + len(page["Accounts"]).should.be.lower_than_or_equal_to(5) + page["Accounts"][-1]["Name"].should.contain("24") + + @mock_organizations def test_list_accounts_for_parent(): client = boto3.client("organizations", region_name="us-east-1")