From d5af1649523a5f9b95ef4e50a0bdf1828d347395 Mon Sep 17 00:00:00 2001 From: ricardojdsilva87 Date: Sat, 16 Nov 2024 22:15:42 +0000 Subject: [PATCH] feat: support private repository configuration (#265) * feat: support private repository configuration * feat: add tests to dependabot_file.py * fix: remove prettier from Makefile * fix: README lint * fix: README lint * fix: add suggestions --------- Co-authored-by: JM (Jason Meridth) --- .env-example | 1 + .gitignore | 5 +- README.md | 56 ++++++- dependabot_file.py | 152 +++++++++++------- env.py | 17 +- evergreen.py | 55 ++++++- requirements.txt | 2 +- test_dependabot_file.py | 347 +++++++++++++++++++++++++++++----------- test_env.py | 128 ++++++++++++--- test_evergreen.py | 10 +- 10 files changed, 586 insertions(+), 187 deletions(-) diff --git a/.env-example b/.env-example index 879fcc2..02caa01 100644 --- a/.env-example +++ b/.env-example @@ -2,6 +2,7 @@ BATCH_SIZE = "" BODY = "" COMMIT_MESSAGE = "" CREATED_AFTER_DATE = "" +DEPENDABOT_CONFIG_FILE = "" DRY_RUN = "" ENABLE_SECURITY_UPDATES = "" EXEMPT_ECOSYSTEMS = "" diff --git a/.gitignore b/.gitignore index f3ce9c8..eea3c88 100644 --- a/.gitignore +++ b/.gitignore @@ -153,4 +153,7 @@ devenv.local.nix # devenv .envrc devenv.* -.devenv* \ No newline at end of file +.devenv* + +# Local testing files +dependabot-output.yaml diff --git a/README.md b/README.md index 8731682..4a1df1d 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ This action can be configured to authenticate with GitHub App Installation or Pe | `GH_APP_PRIVATE_KEY` | True | `""` | GitHub Application Private Key. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | | `GITHUB_APP_ENTERPRISE_ONLY` | False | false | Set this input to `true` if your app is created in GHE and communicates with GHE. | -The needed GitHub app permissions are the following: +The needed GitHub app permissions are the following under `Repository permissions`: - `Administration` - Read and Write (Needed to activate the [automated security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates#managing-dependabot-security-updates-for-your-repositories) ) - `Pull Requests` - Read and Write (If `TYPE` input is set to `pull`) @@ -125,6 +125,58 @@ The needed GitHub app permissions are the following: | `SCHEDULE` | False | `weekly` | Schedule interval by which to check for dependency updates via Dependabot. Allowed values are `daily`, `weekly`, or `monthly` | | `SCHEDULE_DAY` | False | '' | Scheduled day by which to check for dependency updates via Dependabot. Allowed values are days of the week full names (i.e., `monday`) | | `LABELS` | False | "" | A comma separated list of labels that should be added to pull requests opened by dependabot. | +| `DEPENDABOT_CONFIG_FILE` | False | "" | Location of the configuration file for `dependabot.yml` configurations. If the file is present locally it takes precedence over the one in the repository. | + +### Private repositories configuration + +Dependabot allows the configuration of [private registries](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#configuration-options-for-private-registries) for dependabot to use. +To add a private registry configuration to the dependabot file the `DEPENDABOT_CONFIG_FILE` needs to be set with the path of the configuration file. + +This configuration file needs to exist on the repository where the action runs. It can also be created locally to test some configurations (if created locally it takes precedence over the file on the repository). + +#### Usage + +Set the input variable: + +```yaml +DEPENDABOT_CONFIG_FILE = "dependabot-config.yaml" +``` + +Create a file on your repository in the same path: + +```yaml +npm: + type: "npm" + url: "https://yourprivateregistry/npm/" + username: "${{secrets.username}}" + password: "${{secrets.password}}" + key: + token: + replaces-base: +maven: + type: "maven" + url: "https://yourprivateregistry/maven/" + username: "${{secrets.username}}" + password: "${{secrets.password}}" +``` + +The principal key of each configuration need to match the package managers that the [script is looking for](https://github.com/github/evergreen/blob/main/dependabot_file.py#L78). + +The `dependabot.yaml` created file will look like the following with the `registries:` key added: + +```yaml +updates: + - package-ecosystem: "npm" + directory: "/" + registries: --> added configuration + - 'npm' --> added configuration + schedule: + interval: "weekly" + labels: + - "test" + - "dependabot" + - "new" +``` ### Example workflows @@ -225,7 +277,7 @@ jobs: GH_APP_PRIVATE_KEY: ${{ secrets.GH_APP_PRIVATE_KEY }} # GITHUB_APP_ENTERPRISE_ONLY: True --> Set to true when created GHE App needs to communicate with GHE api GH_ENTERPRISE_URL: ${{ github.server_url }} - # GH_TOKEN: ${{ steps.app-token.outputs.token }} --> the token input is not used if the github app inputs are set + # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} --> the token input is not used if the github app inputs are set ORGANIZATION: your_organization UPDATE_EXISTING: True GROUP_DEPENDENCIES: True diff --git a/dependabot_file.py b/dependabot_file.py index d0acbb0..f877ec4 100644 --- a/dependabot_file.py +++ b/dependabot_file.py @@ -1,11 +1,32 @@ """This module contains the function to build the dependabot.yml file for a repo""" +import base64 +import copy +import io + import github3 -import yaml +import ruamel.yaml +from ruamel.yaml.scalarstring import SingleQuotedScalarString + +# Define data structure for dependabot.yaml +data = { + "version": 2, + "registries": {}, + "updates": [], +} + +yaml = ruamel.yaml.YAML() +stream = io.StringIO() def make_dependabot_config( - ecosystem, group_dependencies, indent, schedule, schedule_day, labels + ecosystem, + group_dependencies, + schedule, + schedule_day, + labels, + dependabot_config, + extra_dependabot_config, ) -> str: """ Make the dependabot configuration for a specific package ecosystem @@ -13,40 +34,70 @@ def make_dependabot_config( Args: ecosystem: the package ecosystem to make the dependabot configuration for group_dependencies: whether to group dependencies in the dependabot.yml file - indent: the number of spaces to indent the dependabot configuration ex: " " schedule: the schedule to run dependabot ex: "daily" schedule_day: the day of the week to run dependabot ex: "monday" if schedule is "weekly" labels: the list of labels to be added to dependabot configuration + dependabot_config: extra dependabot configs + extra_dependabot_config: File with the configuration to add dependabot configs (ex: private registries) Returns: str: the dependabot configuration for the package ecosystem """ - schedule_day_line = "" - if schedule_day: - schedule_day_line += f""" -{indent}{indent}{indent}day: '{schedule_day}'""" - dependabot_config = f"""{indent}- package-ecosystem: '{ecosystem}' -{indent}{indent}directory: '/' -{indent}{indent}schedule: -{indent}{indent}{indent}interval: '{schedule}'{schedule_day_line} -""" + dependabot_config["updates"].append( + { + "package-ecosystem": SingleQuotedScalarString(ecosystem), + "directory": SingleQuotedScalarString("/"), + } + ) + + if extra_dependabot_config: + ecosystem_config = extra_dependabot_config.get(ecosystem) + if ecosystem_config: + dependabot_config["registries"][ecosystem] = ecosystem_config + dependabot_config["updates"][-1].update( + {"registries": [SingleQuotedScalarString(ecosystem)]} + ) + else: + dependabot_config.pop("registries", None) + + if schedule_day: + dependabot_config["updates"][-1].update( + { + "schedule": { + "interval": SingleQuotedScalarString(schedule), + "day": SingleQuotedScalarString(schedule_day), + }, + } + ) + else: + dependabot_config["updates"][-1].update( + { + "schedule": {"interval": SingleQuotedScalarString(schedule)}, + } + ) if labels: - dependabot_config += f"""{indent}{indent}labels: -""" + quoted_labels = [] for label in labels: - dependabot_config += f"""{indent}{indent}{indent}- \"{label}\" -""" + quoted_labels.append(SingleQuotedScalarString(label)) + dependabot_config["updates"][-1].update({"labels": quoted_labels}) if group_dependencies: - dependabot_config += f"""{indent}{indent}groups: -{indent}{indent}{indent}production-dependencies: -{indent}{indent}{indent}{indent}dependency-type: 'production' -{indent}{indent}{indent}development-dependencies: -{indent}{indent}{indent}{indent}dependency-type: 'development' -""" - return dependabot_config + dependabot_config["updates"][-1].update( + { + "groups": { + "production-dependencies": { + "dependency-type": SingleQuotedScalarString("production") + }, + "development-dependencies": { + "dependency-type": SingleQuotedScalarString("development") + }, + } + } + ) + + return yaml.dump(dependabot_config, stream) def build_dependabot_file( @@ -58,6 +109,7 @@ def build_dependabot_file( schedule, schedule_day, labels, + extra_dependabot_config, ) -> str | None: """ Build the dependabot.yml file for a repo based on the repo contents @@ -71,6 +123,7 @@ def build_dependabot_file( schedule: the schedule to run dependabot ex: "daily" schedule_day: the day of the week to run dependabot ex: "monday" if schedule is "daily" labels: the list of labels to be added to dependabot configuration + extra_dependabot_config: File with the configuration to add dependabot configs (ex: private registries) Returns: str: the dependabot.yml file for the repo @@ -89,30 +142,20 @@ def build_dependabot_file( "github-actions": False, "maven": False, } - DEFAULT_INDENT = 2 # pylint: disable=invalid-name + # create a local copy in order to avoid overwriting the global exemption list exempt_ecosystems_list = exempt_ecosystems.copy() if existing_config: - dependabot_file = existing_config.decoded.decode("utf-8") - ecosystem_line = next( - line - for line in dependabot_file.splitlines() - if "- package-ecosystem:" in line - ) - indent = " " * (len(ecosystem_line) - len(ecosystem_line.lstrip())) - if len(indent) < DEFAULT_INDENT: - print( - f"Invalid dependabot.yml file. No indentation found. Skipping {repo.full_name}" - ) - return None + yaml.preserve_quotes = True + try: + dependabot_file = yaml.load(base64.b64decode(existing_config.content)) + except ruamel.yaml.YAMLError as e: + print(f"YAML indentation error: {e}") + raise else: - indent = " " * DEFAULT_INDENT - dependabot_file = """--- -version: 2 -updates: -""" + dependabot_file = copy.deepcopy(data) - add_existing_ecosystem_to_exempt_list(exempt_ecosystems_list, existing_config) + add_existing_ecosystem_to_exempt_list(exempt_ecosystems_list, dependabot_file) # If there are repository specific exemptions, # overwrite the global exemptions for this repo only @@ -151,17 +194,14 @@ def build_dependabot_file( try: if repo.file_contents(file): package_managers_found[manager] = True - # If the last thing in the file is not a newline, - # add one before adding a new language config to the file - if dependabot_file and dependabot_file[-1] != "\n": - dependabot_file += "\n" - dependabot_file += make_dependabot_config( + make_dependabot_config( manager, group_dependencies, - indent, schedule, schedule_day, labels, + dependabot_file, + extra_dependabot_config, ) break except github3.exceptions.NotFoundError: @@ -173,13 +213,14 @@ def build_dependabot_file( for file in repo.directory_contents("/"): if file[0].endswith(".tf"): package_managers_found["terraform"] = True - dependabot_file += make_dependabot_config( + make_dependabot_config( "terraform", group_dependencies, - indent, schedule, schedule_day, labels, + dependabot_file, + extra_dependabot_config, ) break except github3.exceptions.NotFoundError: @@ -189,13 +230,14 @@ def build_dependabot_file( for file in repo.directory_contents(".github/workflows"): if file[0].endswith(".yml") or file[0].endswith(".yaml"): package_managers_found["github-actions"] = True - dependabot_file += make_dependabot_config( + make_dependabot_config( "github-actions", group_dependencies, - indent, schedule, schedule_day, labels, + dependabot_file, + extra_dependabot_config, ) break except github3.exceptions.NotFoundError: @@ -212,7 +254,5 @@ def add_existing_ecosystem_to_exempt_list(exempt_ecosystems, existing_config): to the exempt list so we don't get duplicate entries and maintain configuration settings """ if existing_config: - existing_config_obj = yaml.safe_load(existing_config.decoded) - if existing_config_obj: - for entry in existing_config_obj.get("updates", []): - exempt_ecosystems.append(entry["package-ecosystem"]) + for entry in existing_config.get("updates", []): + exempt_ecosystems.append(entry["package-ecosystem"]) diff --git a/env.py b/env.py index a347c40..76e5d47 100644 --- a/env.py +++ b/env.py @@ -8,6 +8,10 @@ from dotenv import load_dotenv +MAX_TITLE_LENGTH = 70 +MAX_BODY_LENGTH = 65536 +MAX_COMMIT_MESSAGE_LENGTH = 65536 + def get_bool_env_var(env_var_name: str, default: bool = False) -> bool: """Get a boolean environment variable. @@ -120,6 +124,7 @@ def get_env_vars( str, str | None, list[str], + str | None, ]: """ Get the environment variables for use in the action. @@ -154,6 +159,7 @@ def get_env_vars( schedule_day (str): The day of the week to run the action on if schedule is daily team_name (str): The team to search for repositories in labels (list[str]): A list of labels to be added to dependabot configuration + dependabot_config_file (str): Dependabot extra configuration file location path """ if not test: @@ -219,15 +225,15 @@ def get_env_vars( follow_up_type = "pull" title = os.getenv("TITLE") - # make sure that title is a string with less than 70 characters + # make sure that title is a string with less than MAX_TITLE_LENGTH characters if title: - if len(title) > 70: + if len(title) > MAX_TITLE_LENGTH: raise ValueError("TITLE environment variable is too long") else: title = "Enable Dependabot" body = os.getenv("BODY") - if body and len(body) > 65536: + if body and len(body) > MAX_BODY_LENGTH: raise ValueError("BODY environment variable is too long") if not body: @@ -247,7 +253,7 @@ def get_env_vars( commit_message = os.getenv("COMMIT_MESSAGE") if commit_message: - if len(commit_message) > 65536: + if len(commit_message) > MAX_COMMIT_MESSAGE_LENGTH: raise ValueError("COMMIT_MESSAGE environment variable is too long") else: commit_message = "Create/Update dependabot.yaml" @@ -337,6 +343,8 @@ def get_env_vars( if labels_str: labels_list = [label.lower().strip() for label in labels_str.split(",")] + dependabot_config_file = os.getenv("DEPENDABOT_CONFIG_FILE") + return ( organization, repositories_list, @@ -365,4 +373,5 @@ def get_env_vars( schedule_day, team_name, labels_list, + dependabot_config_file, ) diff --git a/evergreen.py b/evergreen.py index 7e45724..a5e3a7c 100644 --- a/evergreen.py +++ b/evergreen.py @@ -1,5 +1,8 @@ """This file contains the main() and other functions needed to open an issue/PR dependabot is not enabled but could be""" +import base64 +import io +import os import sys import uuid from datetime import datetime @@ -8,6 +11,7 @@ import env import github3 import requests +import ruamel.yaml from dependabot_file import build_dependabot_file @@ -43,6 +47,7 @@ def main(): # pragma: no cover schedule_day, team_name, labels, + dependabot_config_file, ) = env.get_env_vars() # Auth to GitHub.com or GHE @@ -82,7 +87,7 @@ def main(): # pragma: no cover print(f"Batch size met at {batch_size} eligible repositories.") break - # Check all the things to see if repo is eligble for a pr/issue + # Check all the things to see if repo is eligible for a pr/issue if repo.full_name in exempt_repositories_list: print(f"Skipping {repo.full_name} (exempted)") continue @@ -113,6 +118,35 @@ def main(): # pragma: no cover print(f"Skipping {repo.full_name} (created after filter)") continue + # Check if there is any extra configuration to be added to the dependabot file by checking the DEPENDABOT_CONFIG_FILE env variable + if dependabot_config_file: + yaml = ruamel.yaml.YAML() + yaml.preserve_quotes = True + # If running locally on a computer the local file takes precedence over the one existent on the repository + if os.path.exists(dependabot_config_file): + try: + with open( + dependabot_config_file, "r", encoding="utf-8" + ) as extra_dependabot_config: + extra_dependabot_config = yaml.load(extra_dependabot_config) + except ruamel.yaml.YAMLError as e: + print(f"YAML indentation error: {e}") + continue + else: + try: + extra_dependabot_config = check_existing_config( + repo, dependabot_config_file + ).content + extra_dependabot_config = yaml.load( + base64.b64decode(extra_dependabot_config) + ) + except ruamel.yaml.YAMLError as e: + print(f"YAML indentation error: {e}") + continue + else: + # If no dependabot configuration file is present set the variable empty + extra_dependabot_config = None + print(f"Checking {repo.full_name} for compatible package managers") # Try to detect package managers and build a dependabot file dependabot_file = build_dependabot_file( @@ -124,11 +158,22 @@ def main(): # pragma: no cover schedule, schedule_day, labels, + extra_dependabot_config, ) + yaml = ruamel.yaml.YAML() + stream = io.StringIO() + yaml.indent(mapping=2, sequence=4, offset=2) + + # create locally the dependabot file + with open("dependabot-output.yaml", "w", encoding="utf-8") as yaml_file: + yaml.dump(dependabot_file, yaml_file) + if dependabot_file is None: print("\tNo (new) compatible package manager found") - continue + else: + dependabot_file = yaml.dump(dependabot_file, stream) + dependabot_file = stream.getvalue() # If dry_run is set, just print the dependabot file if dry_run: @@ -227,12 +272,12 @@ def is_dependabot_security_updates_enabled(ghe, owner, repo, access_token): def check_existing_config(repo, filename): """ - Check if the dependabot file already exists in the + Check if a file already exists in the repository and return the existing config if it does Args: repo (github3.repos.repo.Repository): The repository to check - filename (str): The dependabot configuration filename to check + filename (str): The configuration filename to check Returns: github3.repos.contents.Contents | None: The existing config if it exists, otherwise None @@ -321,7 +366,7 @@ def commit_changes( dependabot_filename=".github/dependabot.yml", existing_config=None, ): - """Commit the changes to the repo and open a pull reques and return the pull request object""" + """Commit the changes to the repo and open a pull request and return the pull request object""" default_branch = repo.default_branch # Get latest commit sha from default branch default_branch_commit = repo.ref("heads/" + default_branch).object.sha diff --git a/requirements.txt b/requirements.txt index c9eaeae..4ce4ecf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ github3.py==4.0.1 requests==2.32.3 python-dotenv==1.0.1 -PyYAML==6.0.2 +ruamel.yaml==0.18.6 diff --git a/test_dependabot_file.py b/test_dependabot_file.py index 05489a7..8816538 100644 --- a/test_dependabot_file.py +++ b/test_dependabot_file.py @@ -1,13 +1,17 @@ # pylint: disable=too-many-public-methods """Tests for the dependabot_file.py functions.""" +import base64 +import os import unittest -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import github3 -import yaml +import ruamel.yaml from dependabot_file import add_existing_ecosystem_to_exempt_list, build_dependabot_file +yaml = ruamel.yaml.YAML() + class TestDependabotFile(unittest.TestCase): """ @@ -21,8 +25,8 @@ def test_not_found_error(self): response.status_code = 404 repo.file_contents.side_effect = github3.exceptions.NotFoundError(resp=response) - result = build_dependabot_file(repo, False, [], {}, None, "", "", []) - self.assertEqual(result, None) + result = build_dependabot_file(repo, False, [], {}, None, "", "", [], None) + self.assertIsNone(result) def test_build_dependabot_file_with_schedule_day(self): """Test that the dependabot.yml file is built correctly with weekly schedule day""" @@ -31,7 +35,8 @@ def test_build_dependabot_file_with_schedule_day(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'bundler' @@ -40,8 +45,9 @@ def test_build_dependabot_file_with_schedule_day(self): interval: 'weekly' day: 'tuesday' """ + ) result = build_dependabot_file( - repo, False, [], {}, None, "weekly", "tuesday", [] + repo, False, [], {}, None, "weekly", "tuesday", [], None ) self.assertEqual(result, expected_result) @@ -52,7 +58,8 @@ def test_build_dependabot_file_with_bundler(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'bundler' @@ -60,7 +67,10 @@ def test_build_dependabot_file_with_bundler(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_existing_config_bundler_no_update(self): @@ -71,10 +81,20 @@ def test_build_dependabot_file_with_existing_config_bundler_no_update(self): # expected_result is None because the existing config already contains the all applicable ecosystems expected_result = None existing_config = MagicMock() - existing_config.decoded = b'---\nversion: 2\nupdates:\n - package-ecosystem: "bundler"\n\ - directory: "/"\n schedule:\n interval: "weekly"\n commit-message:\n prefix: "chore(deps)"\n' + existing_config.content = base64.b64encode( + b""" +version: 2 +updates: + - package-ecosystem: "bundler" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "chore(deps)" +""" + ) result = build_dependabot_file( - repo, False, [], {}, existing_config, "weekly", "", [] + repo, False, [], {}, existing_config, "weekly", "", [], None ) self.assertEqual(result, expected_result) @@ -87,7 +107,8 @@ def test_build_dependabot_file_with_2_space_indent_existing_config_bundler_with_ # expected_result maintains existing ecosystem with custom configuration # and adds new ecosystem - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: "pip" @@ -101,24 +122,10 @@ def test_build_dependabot_file_with_2_space_indent_existing_config_bundler_with_ schedule: interval: 'weekly' """ - existing_config = MagicMock() - existing_config.decoded = b'---\nversion: 2\nupdates:\n - package-ecosystem: "pip"\n directory: "/"\n\ - schedule:\n interval: "weekly"\n commit-message:\n prefix: "chore(deps)"\n' - result = build_dependabot_file( - repo, False, [], {}, existing_config, "weekly", "", [] ) - self.assertEqual(result, expected_result) - - def test_build_dependabot_file_with_2_space_indent_existing_config_bundler_with_update_and_no_newline( - self, - ): - """Test that the dependabot.yml file is built correctly with bundler""" - repo = MagicMock() - repo.file_contents.side_effect = lambda f, filename="Gemfile": f == filename - - # expected_result maintains existing ecosystem with custom configuration - # and adds new ecosystem - expected_result = """--- + existing_config = MagicMock() + existing_config.content = base64.b64encode( + b""" version: 2 updates: - package-ecosystem: "pip" @@ -127,16 +134,10 @@ def test_build_dependabot_file_with_2_space_indent_existing_config_bundler_with_ interval: "weekly" commit-message: prefix: "chore(deps)" - - package-ecosystem: 'bundler' - directory: '/' - schedule: - interval: 'weekly' """ - existing_config = MagicMock() - existing_config.decoded = b'---\nversion: 2\nupdates:\n - package-ecosystem: "pip"\n directory: "/"\n\ - schedule:\n interval: "weekly"\n commit-message:\n prefix: "chore(deps)"' + ) result = build_dependabot_file( - repo, False, [], {}, existing_config, "weekly", "", [] + repo, False, [], {}, existing_config, "weekly", "", [], None ) self.assertEqual(result, expected_result) @@ -150,12 +151,97 @@ def test_build_dependabot_file_with_weird_space_indent_existing_config_bundler_w # expected_result maintains existing ecosystem with custom configuration # and adds new ecosystem existing_config = MagicMock() - existing_config.decoded = b'---\nversion: 2\nupdates:\n- package-ecosystem: "pip"\n directory: "/"\n\ - schedule:\n interval: "weekly"\n commit-message:\n prefix: "chore(deps)"\n' + existing_config.content = base64.b64encode( + b""" +version: 2 +updates: +- package-ecosystem: "pip" +directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "chore(deps)" + """ + ) + + with self.assertRaises(ruamel.yaml.YAMLError): + build_dependabot_file( + repo, False, [], {}, existing_config, "weekly", "", [], None + ) + + def test_build_dependabot_file_with_incorrect_indentation_in_extra_dependabot_config_file( + self, + ): + """Test incorrect indentation on extra_dependabot_config""" + repo = MagicMock() + repo.file_contents.side_effect = lambda f, filename="Gemfile": f == filename + + # expected_result maintains existing ecosystem with custom configuration + # and adds new ecosystem + extra_dependabot_config = MagicMock() + extra_dependabot_config.content = base64.b64encode( + b""" +npm: +type: 'npm' + url: 'https://yourprivateregistry/npm/' + username: '${{secrets.username}}' + password: '${{secrets.password}}' + """ + ) + + with self.assertRaises(ruamel.yaml.YAMLError): + build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], extra_dependabot_config + ) + + @patch.dict(os.environ, {"DEPENDABOT_CONFIG_FILE": "dependabot-config.yaml"}) + def test_build_dependabot_file_with_extra_dependabot_config_file(self): + """Test that the dependabot.yaml file is built correctly with extra configurations from extra_dependabot_config""" + + repo = MagicMock() + repo.file_contents.side_effect = ( + lambda f, filename="package.json": f == filename + ) + + # expected_result maintains existing ecosystem with custom configuration + # and adds new ecosystem + extra_dependabot_config = MagicMock() + extra_dependabot_config.content = base64.b64encode( + b""" +npm: + type: 'npm' + url: 'https://yourprivateregistry/npm/' + username: '${{secrets.username}}' + password: '${{secrets.password}}' + """ + ) + extra_dependabot_config = yaml.load( + base64.b64decode(extra_dependabot_config.content) + ) + + expected_result = yaml.load( + b""" +version: 2 +registries: + npm: + type: 'npm' + url: 'https://yourprivateregistry/npm/' + username: '${{secrets.username}}' + password: '${{secrets.password}}' +updates: + - package-ecosystem: "npm" + directory: "/" + registries: + - 'npm' + schedule: + interval: "weekly" +""" + ) + result = build_dependabot_file( - repo, False, [], {}, existing_config, "weekly", "", [] + repo, False, [], {}, None, "weekly", "", [], extra_dependabot_config ) - self.assertEqual(result, None) + self.assertEqual(result, expected_result) def test_build_dependabot_file_with_npm(self): """Test that the dependabot.yml file is built correctly with npm""" @@ -164,7 +250,8 @@ def test_build_dependabot_file_with_npm(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'npm' @@ -172,7 +259,10 @@ def test_build_dependabot_file_with_npm(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_pip(self): @@ -188,7 +278,8 @@ def test_build_dependabot_file_with_pip(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'pip' @@ -196,7 +287,10 @@ def test_build_dependabot_file_with_pip(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_cargo(self): @@ -209,7 +303,8 @@ def test_build_dependabot_file_with_cargo(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'cargo' @@ -217,7 +312,10 @@ def test_build_dependabot_file_with_cargo(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_gomod(self): @@ -225,7 +323,8 @@ def test_build_dependabot_file_with_gomod(self): repo = MagicMock() repo.file_contents.side_effect = lambda filename: filename == "go.mod" - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'gomod' @@ -233,7 +332,10 @@ def test_build_dependabot_file_with_gomod(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_composer(self): @@ -246,7 +348,8 @@ def test_build_dependabot_file_with_composer(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'composer' @@ -254,7 +357,10 @@ def test_build_dependabot_file_with_composer(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_hex(self): @@ -267,7 +373,8 @@ def test_build_dependabot_file_with_hex(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'mix' @@ -275,7 +382,10 @@ def test_build_dependabot_file_with_hex(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_nuget(self): @@ -283,7 +393,8 @@ def test_build_dependabot_file_with_nuget(self): repo = MagicMock() repo.file_contents.side_effect = lambda filename: filename.endswith(".csproj") - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'nuget' @@ -291,7 +402,10 @@ def test_build_dependabot_file_with_nuget(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_docker(self): @@ -299,7 +413,8 @@ def test_build_dependabot_file_with_docker(self): repo = MagicMock() repo.file_contents.side_effect = lambda filename: filename == "Dockerfile" - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'docker' @@ -307,7 +422,10 @@ def test_build_dependabot_file_with_docker(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_maven(self): @@ -315,7 +433,8 @@ def test_build_dependabot_file_with_maven(self): repo = MagicMock() repo.file_contents.side_effect = lambda filename: filename == "pom.xml" - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'maven' @@ -323,7 +442,10 @@ def test_build_dependabot_file_with_maven(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_terraform_with_files(self): @@ -336,7 +458,8 @@ def test_build_dependabot_file_with_terraform_with_files(self): [("main.tf", None)] if path == "/" else [] ) - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'terraform' @@ -344,7 +467,10 @@ def test_build_dependabot_file_with_terraform_with_files(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_terraform_without_files(self): @@ -356,7 +482,9 @@ def test_build_dependabot_file_with_terraform_without_files(self): # Test absence of Terraform files repo.directory_contents.side_effect = lambda path: [] if path == "/" else [] - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertIsNone(result) # Test empty repository @@ -365,7 +493,9 @@ def test_build_dependabot_file_with_terraform_without_files(self): repo.directory_contents.side_effect = github3.exceptions.NotFoundError( resp=response ) - result = build_dependabot_file(repo, False, [], {}, None, "weekly", "", []) + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "", [], None + ) self.assertIsNone(result) def test_build_dependabot_file_with_github_actions(self): @@ -378,7 +508,8 @@ def test_build_dependabot_file_with_github_actions(self): [("test.yml", None)] if path == ".github/workflows" else [] ) - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'github-actions' @@ -386,7 +517,10 @@ def test_build_dependabot_file_with_github_actions(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], None, None, "weekly", "", []) + ) + result = build_dependabot_file( + repo, False, [], None, None, "weekly", "", [], None + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_github_actions_without_files(self): @@ -399,15 +533,18 @@ def test_build_dependabot_file_with_github_actions_without_files(self): resp=response ) - result = build_dependabot_file(repo, False, [], None, None, "weekly", "", []) - self.assertEqual(result, None) + result = build_dependabot_file( + repo, False, [], None, None, "weekly", "", [], None + ) + self.assertIsNone(result) def test_build_dependabot_file_with_groups(self): """Test that the dependabot.yml file is built correctly with grouped dependencies""" repo = MagicMock() repo.file_contents.side_effect = lambda filename: filename == "Dockerfile" - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'docker' @@ -420,7 +557,8 @@ def test_build_dependabot_file_with_groups(self): development-dependencies: dependency-type: 'development' """ - result = build_dependabot_file(repo, True, [], {}, None, "weekly", "", []) + ) + result = build_dependabot_file(repo, True, [], {}, None, "weekly", "", [], None) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_exempt_ecosystems(self): @@ -429,9 +567,9 @@ def test_build_dependabot_file_with_exempt_ecosystems(self): repo.file_contents.side_effect = lambda filename: filename == "Dockerfile" result = build_dependabot_file( - repo, False, ["docker"], {}, None, "weekly", "", [] + repo, False, ["docker"], {}, None, "weekly", "", [], None ) - self.assertEqual(result, None) + self.assertIsNone(result) def test_build_dependabot_file_with_repo_specific_exempt_ecosystems(self): """Test that the dependabot.yml file is built correctly with exempted ecosystems""" @@ -440,23 +578,21 @@ def test_build_dependabot_file_with_repo_specific_exempt_ecosystems(self): repo.file_contents.side_effect = lambda filename: filename == "Dockerfile" result = build_dependabot_file( - repo, False, [], {"test/test": ["docker"]}, None, "weekly", "", [] + repo, False, [], {"test/test": ["docker"]}, None, "weekly", "", [], None ) - self.assertEqual(result, None) + self.assertIsNone(result) def test_add_existing_ecosystem_to_exempt_list(self): """Test that existing ecosystems are added to the exempt list""" exempt_ecosystems = ["npm", "pip", "github-actions"] - existing_config = MagicMock() - existing_config.decoded = yaml.dump( - { - "updates": [ - {"package-ecosystem": "npm"}, - {"package-ecosystem": "pip"}, - {"package-ecosystem": "bundler"}, - ] - } - ).encode() + + existing_config = { + "updates": [ + {"package-ecosystem": "npm"}, + {"package-ecosystem": "pip"}, + {"package-ecosystem": "bundler"}, + ] + } add_existing_ecosystem_to_exempt_list(exempt_ecosystems, existing_config) @@ -471,13 +607,23 @@ def test_build_dependabot_file_for_multiple_repos_with_few_existing_config(self) Test the case where there are multiple repos with few existing dependabot config """ existing_config_repo = MagicMock() + existing_config_repo.file_contents.side_effect = ( lambda f, filename="Gemfile": f == filename ) existing_config = MagicMock() - existing_config.decoded = b'---\nversion: 2\nupdates:\n - package-ecosystem: "bundler"\n\ - directory: "/"\n schedule:\n interval: "weekly"\n commit-message:\n prefix: "chore(deps)"\n' + existing_config.content = base64.b64encode( + b""" +version: 2 +updates: + - package-ecosystem: 'bundler' + directory: '/' + schedule: + interval: 'weekly' +""" + ) + exempt_ecosystems = [] result = build_dependabot_file( existing_config_repo, @@ -488,8 +634,9 @@ def test_build_dependabot_file_for_multiple_repos_with_few_existing_config(self) "weekly", "", [], + None, ) - self.assertEqual(result, None) + self.assertIsNone(result) no_existing_config_repo = MagicMock() filename_list = ["package.json", "package-lock.json", "yarn.lock"] @@ -497,7 +644,9 @@ def test_build_dependabot_file_for_multiple_repos_with_few_existing_config(self) no_existing_config_repo.file_contents.side_effect = ( lambda f, filename=filename: f == filename ) - expected_result = """--- + yaml.preserve_quotes = True + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'npm' @@ -505,6 +654,7 @@ def test_build_dependabot_file_for_multiple_repos_with_few_existing_config(self) schedule: interval: 'weekly' """ + ) result = build_dependabot_file( no_existing_config_repo, False, @@ -514,6 +664,7 @@ def test_build_dependabot_file_for_multiple_repos_with_few_existing_config(self) "weekly", "", [], + None, ) self.assertEqual(result, expected_result) @@ -524,7 +675,8 @@ def test_check_multiple_repos_with_no_dependabot_config(self): mock_repo_1 = MagicMock() mock_repo_1.file_contents.side_effect = lambda filename: filename == "go.mod" - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'gomod' @@ -532,9 +684,10 @@ def test_check_multiple_repos_with_no_dependabot_config(self): schedule: interval: 'weekly' """ + ) exempt_ecosystems = [] result = build_dependabot_file( - mock_repo_1, False, exempt_ecosystems, {}, None, "weekly", "", [] + mock_repo_1, False, exempt_ecosystems, {}, None, "weekly", "", [], None ) self.assertEqual(result, expected_result) @@ -544,7 +697,8 @@ def test_check_multiple_repos_with_no_dependabot_config(self): no_existing_config_repo.file_contents.side_effect = ( lambda f, filename=filename: f == filename ) - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'npm' @@ -552,6 +706,7 @@ def test_check_multiple_repos_with_no_dependabot_config(self): schedule: interval: 'weekly' """ + ) result = build_dependabot_file( no_existing_config_repo, False, @@ -561,6 +716,7 @@ def test_check_multiple_repos_with_no_dependabot_config(self): "weekly", "", [], + None, ) self.assertEqual(result, expected_result) @@ -571,7 +727,8 @@ def test_build_dependabot_file_with_label(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'bundler' @@ -581,8 +738,9 @@ def test_build_dependabot_file_with_label(self): labels: - "dependencies" """ + ) result = build_dependabot_file( - repo, False, [], {}, None, "weekly", "", ["dependencies"] + repo, False, [], {}, None, "weekly", "", ["dependencies"], None ) self.assertEqual(result, expected_result) @@ -593,7 +751,8 @@ def test_build_dependabot_file_with_labels(self): for filename in filename_list: repo.file_contents.side_effect = lambda f, filename=filename: f == filename - expected_result = """--- + expected_result = yaml.load( + b""" version: 2 updates: - package-ecosystem: 'bundler' @@ -605,6 +764,7 @@ def test_build_dependabot_file_with_labels(self): - "test1" - "test2" """ + ) result = build_dependabot_file( repo, False, @@ -614,6 +774,7 @@ def test_build_dependabot_file_with_labels(self): "weekly", "", ["dependencies", "test1", "test2"], + None, ) self.assertEqual(result, expected_result) diff --git a/test_env.py b/test_env.py index b93adf4..d3e2331 100644 --- a/test_env.py +++ b/test_env.py @@ -3,10 +3,17 @@ """Test the get_env_vars function""" import os +import random +import string import unittest from unittest.mock import patch -from env import get_env_vars +from env import ( + MAX_BODY_LENGTH, + MAX_COMMIT_MESSAGE_LENGTH, + MAX_TITLE_LENGTH, + get_env_vars, +) class TestEnv(unittest.TestCase): @@ -86,6 +93,7 @@ def test_get_env_vars_with_org(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -140,6 +148,7 @@ def test_get_env_vars_with_org_and_repo_specific_exemptions(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -251,6 +260,7 @@ def test_get_env_vars_with_repos(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -307,6 +317,7 @@ def test_get_env_vars_with_team(self): "", # schedule_day "engineering", # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -380,6 +391,7 @@ def test_get_env_vars_optional_values(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -425,6 +437,7 @@ def test_get_env_vars_with_update_existing(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -484,6 +497,7 @@ def test_get_env_vars_auth_with_github_app_installation(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -572,6 +586,7 @@ def test_get_env_vars_with_repos_no_dry_run(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -617,6 +632,7 @@ def test_get_env_vars_with_repos_disabled_security_updates(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -663,6 +679,7 @@ def test_get_env_vars_with_repos_filter_visibility_multiple_values(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -709,6 +726,7 @@ def test_get_env_vars_with_repos_filter_visibility_single_value(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -785,6 +803,7 @@ def test_get_env_vars_with_repos_filter_visibility_no_duplicates(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -832,6 +851,7 @@ def test_get_env_vars_with_repos_exempt_ecosystems(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -878,6 +898,7 @@ def test_get_env_vars_with_no_batch_size(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -925,6 +946,7 @@ def test_get_env_vars_with_batch_size(self): "", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -1061,6 +1083,7 @@ def test_get_env_vars_with_valid_schedule_and_schedule_day(self): "tuesday", # schedule_day None, # team_name [], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -1104,25 +1127,6 @@ def test_get_env_vars_with_incorrect_type(self): "TYPE environment variable not 'issue' or 'pull'", ) - @patch.dict( - os.environ, - { - "ORGANIZATION": "my_organization", - "GH_TOKEN": "my_token", - "TITLE": "This is a really long title to test if the limit is set to a maximum number of characters supported by github", - }, - clear=True, - ) - def test_get_env_vars_with_long_title(self): - """Test incorrect type error, should be issue or pull""" - with self.assertRaises(ValueError) as context_manager: - get_env_vars(True) - the_exception = context_manager.exception - self.assertEqual( - str(the_exception), - "TITLE environment variable is too long", - ) - @patch.dict( os.environ, { @@ -1164,6 +1168,7 @@ def test_get_env_vars_with_a_valid_label(self): "", # schedule_day None, # team_name ["dependencies"], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -1209,10 +1214,93 @@ def test_get_env_vars_with_valid_labels_containing_spaces(self): "", # schedule_day None, # team_name ["dependencies", "test", "test2"], # labels + None, ) result = get_env_vars(True) self.assertEqual(result, expected_result) + @patch.dict( + os.environ, + { + "ORGANIZATION": "my_organization", + "GH_TOKEN": "test", + "COMMIT_MESSAGE": "".join( + random.choices(string.ascii_letters, k=MAX_COMMIT_MESSAGE_LENGTH + 1) + ), + }, + clear=True, + ) + def test_get_env_vars_commit_message_too_long(self): + """Test that an error is raised when the COMMIT_MESSAGE env variable has more than MAX_COMMIT_MESSAGE_LENGTH characters""" + with self.assertRaises(ValueError) as context_manager: + get_env_vars(True) + the_exception = context_manager.exception + self.assertEqual( + str(the_exception), + "COMMIT_MESSAGE environment variable is too long", + ) + + @patch.dict( + os.environ, + { + "ORGANIZATION": "my_organization", + "GH_TOKEN": "test", + "BODY": "".join( + random.choices(string.ascii_letters, k=MAX_BODY_LENGTH + 1) + ), + }, + clear=True, + ) + def test_get_env_vars_pr_body_too_long(self): + """Test that an error is raised when the BODY env variable has more than MAX_BODY_LENGTH characters""" + with self.assertRaises(ValueError) as context_manager: + get_env_vars(True) + the_exception = context_manager.exception + self.assertEqual( + str(the_exception), + "BODY environment variable is too long", + ) + + @patch.dict( + os.environ, + { + "ORGANIZATION": "my_organization", + "GH_TOKEN": "my_token", + "TITLE": "".join( + random.choices(string.ascii_letters, k=MAX_TITLE_LENGTH + 1) + ), + }, + clear=True, + ) + def test_get_env_vars_with_long_title(self): + """Test incorrect type error, should be issue or pull""" + with self.assertRaises(ValueError) as context_manager: + get_env_vars(True) + the_exception = context_manager.exception + self.assertEqual( + str(the_exception), + "TITLE environment variable is too long", + ) + + @patch.dict( + os.environ, + { + "ORGANIZATION": "my_organization", + "GH_TOKEN": "my_token", + "PROJECT_ID": "project_name", + }, + clear=True, + ) + def test_get_env_vars_project_id_not_a_number(self): + """Test incorrect type error, should be issue or pull""" + with self.assertRaises(ValueError) as context_manager: + get_env_vars(True) + the_exception = context_manager.exception + self.assertEqual( + str(the_exception), + "PROJECT_ID environment variable is not numeric", + ) + if __name__ == "__main__": unittest.main() diff --git a/test_evergreen.py b/test_evergreen.py index 4402d96..f0a9a0e 100644 --- a/test_evergreen.py +++ b/test_evergreen.py @@ -266,7 +266,7 @@ def test_check_pending_pulls_for_duplicates_no_duplicates(self): result = check_pending_pulls_for_duplicates("dependabot-branch", mock_repo) # Assert that the function returned the expected result - self.assertEqual(result, False) + self.assertFalse(result) def test_check_pending_pulls_for_duplicates_with_duplicates(self): """Test the check_pending_pulls_for_duplicates function where there are duplicates to be found.""" @@ -278,7 +278,7 @@ def test_check_pending_pulls_for_duplicates_with_duplicates(self): result = check_pending_pulls_for_duplicates(mock_pull_request.title, mock_repo) # Assert that the function returned the expected result - self.assertEqual(result, True) + self.assertTrue(result) class TestCheckPendingIssuesForDuplicates(unittest.TestCase): @@ -295,7 +295,7 @@ def test_check_pending_issues_for_duplicates_no_duplicates(self): mock_issue.issues.assert_called_once_with(state="open") # Assert that the function returned the expected result - self.assertEqual(result, False) + self.assertFalse(result) def test_check_pending_issues_for_duplicates_with_duplicates(self): """Test the check_pending_issues_for_duplicates function where there are duplicates to be found.""" @@ -308,7 +308,7 @@ def test_check_pending_issues_for_duplicates_with_duplicates(self): mock_issue.issues.assert_called_once_with(state="open") # Assert that the function returned the expected result - self.assertEqual(result, True) + self.assertTrue(result) class TestGetReposIterator(unittest.TestCase): @@ -686,7 +686,7 @@ def test_is_created_after_date_is_empty_string(self): self.assertFalse(result) - def test_is_repo_created_date_is_before_created_after_date_without_timezene_again( + def test_is_repo_created_date_is_before_created_after_date_without_timezone_again( self, ): """Test the repo.created_at date is before created_after_date without timezone again."""