def test_digital_garden_changes_dont_select_last_year_published_changes() -> None:
    """
    Given: A mkdocs repo with changes done in the last and current years, and last
        year changes have been already published.
    When: changes_to_publish is called.
    Then: No changes are going to be published
    """
    last_year_change = Change(
        date=datetime(2020, 1, 2, tzinfo=tz.tzlocal()),
        summary="Add funny emojis.",
        type_="feature",
        scope=None,
    )
    this_year_change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Add ash, birch and beech information.",
        type_="feature",
        scope="botany",
    )
    changes = [last_year_change, this_year_change]
    last_published = LastNewsletter(yearly=datetime(2021, 1, 1, tzinfo=tz.tzlocal()))

    result = digital_garden_changes(changes, last_published)

    assert result.yearly == []
def test_changes_extracts_commits_that_dont_specify_scope(repo: Repo) -> None:
    """
    Given: A mkdocs git repo with a change whose message follows the correct syntax
        but doesn't specify the scope of the change.
    When: changes is called
    Then: The expected Change is returned
    """
    commit_date = datetime.datetime(2021, 2, 2, tzinfo=tz.tzlocal())
    repo.index.add(["docs/emojis.md"])
    repo.index.commit(
        "feat: add funny emojis",
        author=author,
        committer=committer,
        author_date=commit_date,
        commit_date=commit_date,
    )
    expected_change = Change(
        date=commit_date,
        summary="Add funny emojis.",
        type_="feature",
        scope=None,
    )

    result = semantic_changes(repo)

    assert result == [expected_change]
def test_changes_extracts_commits_with_scope_with_spaced_subsection(
        repo: Repo) -> None:
    """
    Given: A mkdocs git repo with a change whose message follows the correct syntax
        and specifies the scope of the change with subsection with spaces in it.
    When: changes is called
    Then: The expected Change is returned
    """
    commit_date = datetime.datetime(2021, 2, 2, tzinfo=tz.tzlocal())
    repo.index.add(["docs/emojis.md"])
    repo.index.commit(
        "feat(emojis#Spaced subsection): add funny emojis",
        author=author,
        committer=committer,
        author_date=commit_date,
        commit_date=commit_date,
    )
    expected_change = Change(
        date=commit_date,
        summary="Add funny emojis.",
        type_="feature",
        scope="emojis#Spaced subsection",
    )

    result = semantic_changes(repo)

    assert result == [expected_change]
def test_create_newsletter_creates_yearly_articles(repo: Repo) -> None:
    """
    Given: a change to publish in the yearly summary.
    When: create_newsletters is called
    Then: The file is created and a File object is returned.
    """
    desired_file = f"{repo.working_dir}/docs/newsletter/2021.md"
    change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Create the introduction page",
        type_="feature",
        scope="index",
        category="Introduction",
        category_order=0,
        file_="index.md",
    )
    file_content = dedent(
        """\
        # [Introduction](index.md)

        * New: Create the introduction page"""
    )
    changes_to_publish = DigitalGardenChanges(yearly=[change])

    result = create_newsletters(changes_to_publish, repo)

    assert result[0] == desired_file
    with open(
        os.path.join(repo.working_dir, "docs/newsletter/2021.md")
    ) as file_descriptor:
        assert file_descriptor.read() == file_content
def test_add_categories_detect_fifth_level_or_above(config: Config) -> None:
    """
    Given: a change whose affected file belongs to a fifth level or above document in
        the nav.
    When: add_change_categories is called.
    Then:
        * The title of the nav is added as the file_section
        * The title of the category is added as category
        * The title of the subcategory is added as subcategory
        * The file name is added under the file_ attribute.
    """
    # ECE001: Expression is too complex, we need to do it this way
    config["nav"][2]["Coding"][1]["Python"][0]["Libraries"].append(  # noqa: ECE001
        {"Data Libraries": [{"DeepDiff": "deepdiff.md"}]}
    )
    change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Add deepdiff",
        type_="feature",
        scope="deepdiff",
    )

    result = add_change_categories([change], config)

    assert result[0].file_ == "deepdiff.md"
    assert result[0].file_section == "DeepDiff"
    assert result[0].file_section_order == 0
    assert result[0].category == "Coding"
    assert result[0].category_order == 2
    assert result[0].subcategory == "Python"
    assert result[0].subcategory_order == 1
def test_add_categories_detect_fourth_level(config: Config) -> None:
    """
    Given: a change whose affected file belongs to a fourth level document in the nav.
    When: add_change_categories is called.
    Then:
        * The title of the nav is added as the file_section
        * The title of the category is added as category
        * The title of the subcategory is added as subcategory
        * The file name is added under the file_ attribute.
    """
    change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Add gitpython",
        type_="feature",
        scope="gitpython",
    )

    result = add_change_categories([change], config)

    assert result[0].file_ == "gitpython.md"
    assert result[0].file_section == "GitPython"
    assert result[0].file_section_order == 0
    assert result[0].category == "Coding"
    assert result[0].category_order == 2
    assert result[0].subcategory == "Python"
    assert result[0].subcategory_order == 1
def test_add_categories_detect_third_level(config: Config) -> None:
    """
    Given: a change whose affected file belongs to a third level document in the nav.
    When: add_change_categories is called.
    Then:
        * The title of the nav is added as the file_section
        * The title of the category is added as category
        * The title of the subcategory is added as subcategory
        * The file name is added under the file_ attribute.
    """
    change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Add helm",
        type_="feature",
        scope="helm",
    )

    result = add_change_categories([change], config)

    assert result[0].file_ == "helm.md"
    assert result[0].category == "DevOps"
    assert result[0].category_order == 1
    assert result[0].subcategory == "Infrastructure as Code"
    assert result[0].subcategory_order == 1
    assert result[0].file_section == "Helm"
    assert result[0].file_section_order == 0
def test_digital_garden_changes_to_publish_selects_last_year_changes() -> None:
    """
    Given: A mkdocs git repo with changes done in the last and the current years.
    When: changes_to_publish is called.
    Then: Only last year changes are selected to be published for the yearly feed.
    """
    last_year_change = Change(
        date=datetime(2020, 1, 2, tzinfo=tz.tzlocal()),
        summary="Add funny emojis.",
        type_="feature",
        scope=None,
    )
    this_year_change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Add ash, birch and beech information.",
        type_="feature",
        scope="botany",
    )
    changes = [last_year_change, this_year_change]

    result = digital_garden_changes(changes)

    assert last_year_change in result.yearly
    assert this_year_change not in result.yearly
def test_digital_garden_ignores_other_change_types(change_type: str) -> None:
    """
    Given: A change with a type_ other than those in CHANGE_TYPE_TEXT.
    When: create_newsletter is called.
    Then: The changes are not part of the output.
    """
    change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="A change that should not be in the newsletter",
        type_=change_type,
        scope="devops",
    )

    result = digital_garden_changes([change])

    assert len(result.daily) == 0
def test_add_categories_extracts_file_subsection(config: Config) -> None:
    """
    Given: a change whose scope contains the subsection information.
    When: add_change_categories is called.
    Then: The file_subsection is extracted and the file_ is correct.
    """
    change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Add gitpython installation",
        type_="feature",
        scope="gitpython#Installation procedure",
    )

    result = add_change_categories([change], config)

    assert result[0].file_ == "gitpython.md"
    assert result[0].file_subsection == "#installation-procedure"
def test_add_categories_groups_changes_with_scope_not_in_nav(config: Config) -> None:
    """
    Given: a change whose scope references a file that is not in the nav.
    When: add_change_categories is called.
    Then: The change is grouped under the Others category as the last category.
    """
    change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Add gitpython installation",
        type_="feature",
        scope="unexistent_scope",
    )

    result = add_change_categories([change], config)

    assert result[0].file_ is None
    assert result[0].category == "Other"
    assert result[0].category_order == 999
    assert result[0].file_section is None
    assert result[0].subcategory is None
def test_add_categories_detect_first_level(config: Config) -> None:
    """
    Given: a change whose affected file belongs to a first level document in the nav.
    When: add_change_categories is called.
    Then: The title of the nav is added as the category and the subcategory and
        file_section are None. The file name is added under the file_ attribute.
    """
    change = Change(
        date=datetime(2021, 2, 8, tzinfo=tz.tzlocal()),
        summary="Add (╯°□°)╯ ┻━┻ emoji",
        type_="feature",
        scope="emojis",
    )

    result = add_change_categories([change], config)

    assert result[0].file_ == "emojis.md"
    assert result[0].category == "Emojis"
    assert result[0].category_order == 4
    assert result[0].file_section is None
    assert result[0].subcategory is None
def test_changes_extracts_commits_with_multiple_changes(repo: Repo) -> None:
    """
    Given: A mkdocs git repo with a change whose message follows the correct syntax,
        contains two semantic changes with scope and description.
    When: changes is called.
    Then: The two expected Changes are returned, where the message respects the
        line breaks.
    """
    commit_date = datetime.datetime(2021, 2, 5, tzinfo=tz.tzlocal())
    repo.index.add(["docs/devops/helm/helm.md", "docs/devops/devops.md"])
    repo.index.commit(
        textwrap.dedent("""\
            feat(helm): introduce Helm the Kubernetes package manager

            [Helm](https://helm.sh/) is the package manager for Kubernetes.
            Through charts it helps you define, install and upgrade even the most
            complex Kubernetes applications.

            feat(devops): define DevOps

            [DevOps](https://en.wikipedia.org/wiki/DevOps) is a set of practices
            that combines software development (Dev) and information-technology
            operations (Ops) which aims to shorten the systems development life
            cycle and provide continuous delivery with high software quality.

            One of the most important goals of the DevOps initiative is to break
            the silos between the developers and the sysadmins, that lead to ill
            feelings and unproductivity."""),
        author=author,
        committer=committer,
        author_date=commit_date,
        commit_date=commit_date,
    )
    expected_changes = [
        Change(
            date=commit_date,
            summary="Introduce Helm the Kubernetes package manager.",
            message=dedent("""\
                [Helm](https://helm.sh/) is the package manager for Kubernetes.
                Through charts it helps you define, install and upgrade even the most
                complex Kubernetes applications."""),
            type_="feature",
            scope="helm",
        ),
        Change(
            date=commit_date,
            summary="Define DevOps.",
            message=dedent("""\
                [DevOps](https://en.wikipedia.org/wiki/DevOps) is a set of practices
                that combines software development (Dev) and information-technology
                operations (Ops) which aims to shorten the systems development life
                cycle and provide continuous delivery with high software quality.

                One of the most important goals of the DevOps initiative is to break
                the silos between the developers and the sysadmins, that lead to ill
                feelings and unproductivity."""),
            type_="feature",
            scope="devops",
        ),
    ]

    result = semantic_changes(repo)

    assert result == expected_changes