{
  "output": "import unittest\nfrom unittest.mock import patch, MagicMock\nimport os\nimport sys\nfrom datetime import date\n\n# Add the project root to sys.path so we can import aws_utils\nsys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), \"..\")))\n\nimport aws_utils\n\n\nclass TestAwsUtils(unittest.TestCase):\n    def test_get_previous_month_range(self):\n        start, end = aws_utils.get_previous_month_range()\n        self.assertTrue(len(start) == 10)  # YYYY-MM-DD\n        self.assertTrue(len(end) == 10)\n        self.assertTrue(start.endswith(\"-01\"))\n        self.assertTrue(end.endswith(\"-01\"))\n\n    def test_get_last_n_months_ranges(self):\n        ranges = aws_utils.get_last_n_months_ranges(3)\n        self.assertEqual(len(ranges), 3)\n        for start, end, label in ranges:\n            self.assertEqual(len(start), 10)\n            self.assertEqual(len(end), 10)\n            self.assertEqual(len(label), 7)  # YYYY-MM\n\n    @patch.dict(os.environ, {\"SKIP_ACCOUNTS\": \"123, 456 \"})\n    def test_get_skip_accounts(self):\n        skipped = aws_utils.get_skip_accounts()\n        self.assertEqual(skipped, [\"123\", \"456\"])\n\n    @patch.dict(os.environ, {\"SKIP_ACCOUNTS\": \"\"})\n    def test_get_skip_accounts_empty(self):\n        skipped = aws_utils.get_skip_accounts()\n        self.assertEqual(skipped, [])\n\n    @patch.dict(os.environ, {\"OU_IDS\": \"ou-1, ou-2\"})\n    def test_get_ou_ids(self):\n        ous = aws_utils.get_ou_ids()\n        self.assertEqual(ous, [\"ou-1\", \"ou-2\"])\n\n    @patch.dict(os.environ, {\"OU_IDS\": \"\"})\n    def test_get_ou_ids_empty(self):\n        ous = aws_utils.get_ou_ids()\n        self.assertEqual(ous, [])\n\n    @patch(\"aws_utils.get_boto_session\")\n    def test_get_account_names(self, mock_get_session):\n        # Mock session and client\n        mock_session = MagicMock()\n        mock_client = MagicMock()\n        mock_get_session.return_value = mock_session\n        mock_session.client.return_value = mock_client\n\n        # Mock pagination\n        mock_paginator = MagicMock()\n        mock_client.get_paginator.return_value = mock_paginator\n        mock_paginator.paginate.return_value = [\n            {\n                \"Accounts\": [\n                    {\"Id\": \"111\", \"Name\": \"Acc1\", \"Status\": \"ACTIVE\"},\n                    {\"Id\": \"222\", \"Name\": \"Acc2\", \"Status\": \"SUSPENDED\"},\n                    {\"Id\": \"333\", \"Name\": \"Acc3\", \"Status\": \"ACTIVE\"},\n                ]\n            }\n        ]\n\n        # Mock skip accounts to be empty for this test\n        with patch(\"aws_utils.get_skip_accounts\", return_value=[]):\n            accounts = aws_utils.get_account_names()\n\n        self.assertEqual(len(accounts), 2)\n        self.assertEqual(accounts[\"111\"], \"Acc1\")\n        self.assertEqual(accounts[\"333\"], \"Acc3\")\n        # Ensure SUSPENDED account is ignored\n        self.assertNotIn(\"222\", accounts)\n\n    @patch(\"aws_utils.get_boto_session\")\n    def test_get_account_names_with_skip(self, mock_get_session):\n        mock_session = MagicMock()\n        mock_client = MagicMock()\n        mock_get_session.return_value = mock_session\n        mock_session.client.return_value = mock_client\n\n        mock_paginator = MagicMock()\n        mock_client.get_paginator.return_value = mock_paginator\n        mock_paginator.paginate.return_value = [\n            {\n                \"Accounts\": [\n                    {\"Id\": \"111\", \"Name\": \"Acc1\", \"Status\": \"ACTIVE\"},\n                    {\"Id\": \"222\", \"Name\": \"Acc2\", \"Status\": \"ACTIVE\"},\n                ]\n            }\n        ]\n\n        with patch(\"aws_utils.get_skip_accounts\", return_value=[\"222\"]):\n            accounts = aws_utils.get_account_names()\n\n        self.assertEqual(len(accounts), 1)\n        self.assertEqual(accounts[\"111\"], \"Acc1\")\n        self.assertNotIn(\"222\", accounts)\n\n    @patch(\"aws_utils.get_boto_session\")\n    def test_setup_org_accounts_session_explicit_ou(self, mock_get_session):\n        mock_session = MagicMock()\n        mock_org_client = MagicMock()\n        mock_get_session.return_value = mock_session\n        mock_session.client.return_value = mock_org_client\n\n        mock_paginator = MagicMock()\n        mock_org_client.get_paginator.return_value = mock_paginator\n        mock_paginator.paginate.return_value = [\n            {\"Accounts\": [{\"Id\": \"111\", \"Name\": \"Acc 1\", \"Status\": \"ACTIVE\"}]}\n        ]\n\n        with patch(\"aws_utils.get_skip_accounts\", return_value=[]):\n            # Iterate through generator\n            results = list(aws_utils.setup_org_accounts_session(ou_ids=[\"ou-test\"]))\n\n        self.assertEqual(len(results), 1)\n        account, profile = results[0]\n        self.assertEqual(account[\"Id\"], \"111\")\n        self.assertEqual(profile, \"Acc-1.admin\")  # Checks sanitization of name\n\n    @patch(\"aws_utils.get_boto_session\")\n    def test_setup_org_accounts_session_fallback(self, mock_get_session):\n        mock_session = MagicMock()\n        mock_org_client = MagicMock()\n        mock_get_session.return_value = mock_session\n        mock_session.client.return_value = mock_org_client\n\n        mock_paginator = MagicMock()\n        mock_org_client.get_paginator.return_value = mock_paginator\n        mock_paginator.paginate.return_value = [\n            {\"Accounts\": [{\"Id\": \"999\", \"Name\": \"FallbackAcc\", \"Status\": \"ACTIVE\"}]}\n        ]\n\n        with (\n            patch(\"aws_utils.get_ou_ids\", return_value=[]),\n            patch(\"aws_utils.get_skip_accounts\", return_value=[]),\n        ):\n            results = list(aws_utils.setup_org_accounts_session())\n\n        self.assertEqual(len(results), 1)\n        account, profile = results[0]\n        self.assertEqual(account[\"Id\"], \"999\")\n        # Check that it called list_accounts (fallback) not list_accounts_for_parent\n        mock_org_client.get_paginator.assert_called_with(\"list_accounts\")\n\n    @patch(\"aws_utils.get_google_auth\")\n    @patch.dict(os.environ, {\"GOOGLE_ORGANIZATION_DOMAIN\": \"example.com\"})\n    def test_export_to_sheets_with_sharing(self, mock_get_google_auth):\n        mock_gc = MagicMock()\n        mock_sh = MagicMock()\n        mock_sh.url = \"http://test-url\"\n        mock_get_google_auth.return_value = mock_gc\n        mock_gc.create.return_value = mock_sh\n\n        # Mock worksheet methods\n        mock_ws = MagicMock()\n        mock_sh.get_worksheet.return_value = mock_ws\n\n        headers = [\"Col1\", \"Col2\"]\n        rows = [[\"A\", \"B\"]]\n\n        url = aws_utils.export_to_sheets(\"test-title\", headers, rows)\n\n        # Verify it created a sheet\n        mock_gc.create.assert_called()\n        # Verify it shared with the domain\n        mock_sh.share.assert_called_with(\n            \"example.com\", perm_type=\"domain\", role=\"reader\"\n        )\n        # Verify it returned the URL\n        self.assertEqual(url, \"http://test-url\")\n\n    @patch(\"aws_utils.get_google_auth\")\n    @patch.dict(os.environ, {\"GOOGLE_ORGANIZATION_DOMAIN\": \"\"})\n    def test_export_to_sheets_no_sharing(self, mock_get_google_auth):\n        mock_gc = MagicMock()\n        mock_sh = MagicMock()\n        mock_get_google_auth.return_value = mock_gc\n        mock_gc.create.return_value = mock_sh\n\n        mock_sh.get_worksheet.return_value = MagicMock()\n\n        aws_utils.export_to_sheets(\"test-title\", [\"H\"], [[\"R\"]])\n\n        # Verify share was NOT called\n        mock_sh.share.assert_not_called()\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
}