mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
1389 lines
56 KiB
Python
1389 lines
56 KiB
Python
from unittest.mock import call as mock_call
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils import timezone
|
|
|
|
from sentry.api.exceptions import InvalidRepository
|
|
from sentry.api.release_search import INVALID_SEMVER_MESSAGE
|
|
from sentry.dynamic_sampling import ProjectBoostedReleases
|
|
from sentry.exceptions import InvalidSearchQuery
|
|
from sentry.models.commit import Commit
|
|
from sentry.models.commitauthor import CommitAuthor
|
|
from sentry.models.environment import Environment
|
|
from sentry.models.group import Group, GroupStatus
|
|
from sentry.models.groupinbox import GroupInbox, GroupInboxReason, add_group_to_inbox
|
|
from sentry.models.grouplink import GroupLink
|
|
from sentry.models.grouprelease import GroupRelease
|
|
from sentry.models.groupresolution import GroupResolution
|
|
from sentry.models.integrations.external_issue import ExternalIssue
|
|
from sentry.models.release import (
|
|
Release,
|
|
ReleaseProject,
|
|
ReleaseProjectModelManager,
|
|
ReleaseStatus,
|
|
follows_semver_versioning_scheme,
|
|
)
|
|
from sentry.models.releasecommit import ReleaseCommit
|
|
from sentry.models.releaseenvironment import ReleaseEnvironment
|
|
from sentry.models.releaseheadcommit import ReleaseHeadCommit
|
|
from sentry.models.releaseprojectenvironment import ReleaseProjectEnvironment
|
|
from sentry.models.repository import Repository
|
|
from sentry.search.events.filter import parse_semver
|
|
from sentry.signals import receivers_raise_on_send
|
|
from sentry.testutils.cases import SetRefsTestCase, TestCase, TransactionTestCase
|
|
from sentry.testutils.factories import Factories
|
|
from sentry.testutils.helpers import Feature
|
|
from sentry.testutils.helpers.datetime import freeze_time
|
|
from sentry.testutils.silo import region_silo_test
|
|
from sentry.utils.strings import truncatechars
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"release_version",
|
|
[
|
|
"[email protected]",
|
|
"[email protected]",
|
|
"[email protected]",
|
|
"[email protected]",
|
|
"[email protected]+43",
|
|
"[email protected]+whatever",
|
|
],
|
|
)
|
|
def test_version_is_semver_valid(release_version):
|
|
assert Release.is_semver_version(release_version) is True
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"release_version",
|
|
[
|
|
"helloworld",
|
|
"alpha@helloworld",
|
|
"[email protected]",
|
|
"[email protected]+12345",
|
|
],
|
|
)
|
|
def test_version_is_semver_invalid(release_version):
|
|
assert Release.is_semver_version(release_version) is False
|
|
|
|
|
|
@region_silo_test
|
|
class MergeReleasesTest(TestCase):
|
|
@receivers_raise_on_send()
|
|
def test_simple(self):
|
|
org = self.create_organization()
|
|
commit = Commit.objects.create(organization_id=org.id, repository_id=5)
|
|
commit2 = Commit.objects.create(organization_id=org.id, repository_id=6)
|
|
|
|
# merge to
|
|
project = self.create_project(organization=org, name="foo")
|
|
environment = Environment.get_or_create(project=project, name="env1")
|
|
release = Release.objects.create(version="abcdabc", organization=org)
|
|
release.add_project(project)
|
|
release_commit = ReleaseCommit.objects.create(
|
|
organization_id=org.id, release=release, commit=commit, order=1
|
|
)
|
|
release_environment = ReleaseEnvironment.objects.create(
|
|
organization_id=org.id,
|
|
project_id=project.id,
|
|
release_id=release.id,
|
|
environment_id=environment.id,
|
|
)
|
|
release_project_environment = ReleaseProjectEnvironment.objects.create(
|
|
release_id=release.id, project_id=project.id, environment_id=environment.id
|
|
)
|
|
group_release = GroupRelease.objects.create(
|
|
project_id=project.id, release_id=release.id, group_id=1
|
|
)
|
|
group = self.create_group(project=project, first_release=release)
|
|
group_resolution = GroupResolution.objects.create(group=group, release=release)
|
|
|
|
# merge from #1
|
|
project2 = self.create_project(organization=org, name="bar")
|
|
environment2 = Environment.get_or_create(project=project2, name="env2")
|
|
release2 = Release.objects.create(version="bbbbbbb", organization=org)
|
|
release2.add_project(project2)
|
|
release_commit2 = ReleaseCommit.objects.create(
|
|
organization_id=org.id, release=release2, commit=commit, order=2
|
|
)
|
|
release_environment2 = ReleaseEnvironment.objects.create(
|
|
organization_id=org.id,
|
|
project_id=project2.id,
|
|
release_id=release2.id,
|
|
environment_id=environment2.id,
|
|
)
|
|
release_project_environment2 = ReleaseProjectEnvironment.objects.create(
|
|
release_id=release2.id, project_id=project2.id, environment_id=environment2.id
|
|
)
|
|
group_release2 = GroupRelease.objects.create(
|
|
project_id=project2.id, release_id=release2.id, group_id=2
|
|
)
|
|
group2 = self.create_group(project=project2, first_release=release2)
|
|
group_resolution2 = GroupResolution.objects.create(group=group2, release=release2)
|
|
|
|
# merge from #2
|
|
project3 = self.create_project(organization=org, name="baz")
|
|
environment3 = Environment.get_or_create(project=project3, name="env3")
|
|
release3 = Release.objects.create(version="cccccc", organization=org)
|
|
release3.add_project(project3)
|
|
release_commit3 = ReleaseCommit.objects.create(
|
|
organization_id=org.id, release=release2, commit=commit2, order=3
|
|
)
|
|
release_environment3 = ReleaseEnvironment.objects.create(
|
|
organization_id=org.id,
|
|
project_id=project3.id,
|
|
release_id=release3.id,
|
|
environment_id=environment3.id,
|
|
)
|
|
release_project_environment3 = ReleaseProjectEnvironment.objects.create(
|
|
release_id=release3.id, project_id=project3.id, environment_id=environment3.id
|
|
)
|
|
group_release3 = GroupRelease.objects.create(
|
|
project_id=project3.id, release_id=release3.id, group_id=3
|
|
)
|
|
group3 = self.create_group(project=project3, first_release=release3)
|
|
group_resolution3 = GroupResolution.objects.create(group=group3, release=release3)
|
|
|
|
Release.merge(release, [release2, release3])
|
|
|
|
# ReleaseCommit.release
|
|
assert ReleaseCommit.objects.get(id=release_commit.id).release == release
|
|
# should not exist because they referenced the same commit
|
|
assert not ReleaseCommit.objects.filter(id=release_commit2.id).exists()
|
|
assert ReleaseCommit.objects.get(id=release_commit3.id).release == release
|
|
|
|
# ReleaseEnvironment.release_id
|
|
assert ReleaseEnvironment.objects.get(id=release_environment.id).release_id == release.id
|
|
assert ReleaseEnvironment.objects.get(id=release_environment2.id).release_id == release.id
|
|
assert ReleaseEnvironment.objects.get(id=release_environment3.id).release_id == release.id
|
|
|
|
# ReleaseProject.release
|
|
assert release.projects.count() == 3
|
|
assert ReleaseProject.objects.filter(release=release, project=project).exists()
|
|
assert ReleaseProject.objects.filter(release=release, project=project2).exists()
|
|
assert ReleaseProject.objects.filter(release=release, project=project3).exists()
|
|
|
|
# ReleaseProjectEnvironment.release
|
|
assert (
|
|
ReleaseProjectEnvironment.objects.get(id=release_project_environment.id).release_id
|
|
== release.id
|
|
)
|
|
assert (
|
|
ReleaseProjectEnvironment.objects.get(id=release_project_environment2.id).release_id
|
|
== release.id
|
|
)
|
|
assert (
|
|
ReleaseProjectEnvironment.objects.get(id=release_project_environment3.id).release_id
|
|
== release.id
|
|
)
|
|
|
|
# GroupRelease.release_id
|
|
assert GroupRelease.objects.get(id=group_release.id).release_id == release.id
|
|
assert GroupRelease.objects.get(id=group_release2.id).release_id == release.id
|
|
assert GroupRelease.objects.get(id=group_release3.id).release_id == release.id
|
|
|
|
# GroupResolution.release
|
|
assert GroupResolution.objects.get(id=group_resolution.id).release == release
|
|
assert GroupResolution.objects.get(id=group_resolution2.id).release == release
|
|
assert GroupResolution.objects.get(id=group_resolution3.id).release == release
|
|
|
|
# Group.first_release
|
|
assert Group.objects.get(id=group.id).first_release == release
|
|
assert Group.objects.get(id=group2.id).first_release == release
|
|
assert Group.objects.get(id=group3.id).first_release == release
|
|
|
|
# Releases are gone
|
|
assert Release.objects.filter(id=release.id).exists()
|
|
assert not Release.objects.filter(id=release2.id).exists()
|
|
assert not Release.objects.filter(id=release3.id).exists()
|
|
|
|
|
|
@region_silo_test
|
|
class SetCommitsTestCase(TestCase):
|
|
@receivers_raise_on_send()
|
|
def test_simple(self):
|
|
org = self.create_organization(owner=Factories.create_user())
|
|
project = self.create_project(organization=org, name="foo")
|
|
group = self.create_group(project=project)
|
|
add_group_to_inbox(group, GroupInboxReason.MANUAL)
|
|
assert GroupInbox.objects.filter(group=group).exists()
|
|
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
commit = Commit.objects.create(
|
|
organization_id=org.id,
|
|
repository_id=repo.id,
|
|
message="fixes %s" % (group.qualified_short_id),
|
|
key="alksdflskdfjsldkfajsflkslk",
|
|
)
|
|
commit2 = Commit.objects.create(
|
|
organization_id=org.id,
|
|
repository_id=repo.id,
|
|
message="i fixed something",
|
|
key="lskfslknsdkcsnlkdflksfdkls",
|
|
)
|
|
|
|
assert GroupLink.objects.filter(
|
|
group_id=group.id, linked_type=GroupLink.LinkedType.commit, linked_id=commit.id
|
|
).exists()
|
|
|
|
release = Release.objects.create(version="abcdabc", organization=org)
|
|
release.add_project(project)
|
|
release.set_commits(
|
|
[
|
|
{"id": commit.key, "repository": repo.name},
|
|
{"id": commit2.key, "repository": repo.name},
|
|
{"id": "a" * 40, "repository": repo.name},
|
|
{"id": "b" * 40, "repository": repo.name, "message": "#skipsentry"},
|
|
]
|
|
)
|
|
|
|
assert ReleaseCommit.objects.filter(commit=commit, release=release).exists()
|
|
assert ReleaseCommit.objects.filter(commit=commit2, release=release).exists()
|
|
|
|
assert Group.objects.get(id=group.id).status == GroupStatus.RESOLVED
|
|
# test that backfilling works
|
|
assert Commit.objects.filter(key="a" * 40, repository_id=repo.id).exists()
|
|
assert not Commit.objects.filter(key="b" * 40, repository_id=repo.id).exists()
|
|
|
|
release = Release.objects.get(id=release.id)
|
|
assert release.commit_count == 3
|
|
assert release.authors == []
|
|
assert release.last_commit_id == commit.id
|
|
|
|
assert ReleaseHeadCommit.objects.filter(
|
|
release_id=release.id, commit_id=commit.id, repository_id=repo.id
|
|
).exists()
|
|
|
|
assert not GroupInbox.objects.filter(group=group).exists()
|
|
|
|
@receivers_raise_on_send()
|
|
def test_backfilling_commits(self):
|
|
org = self.create_organization(owner=Factories.create_user())
|
|
project = self.create_project(organization=org, name="foo")
|
|
group = self.create_group(project=project)
|
|
add_group_to_inbox(group, GroupInboxReason.MANUAL)
|
|
assert GroupInbox.objects.filter(group=group).exists()
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
|
|
commit = Commit.objects.create(repository_id=repo.id, organization_id=org.id, key="b" * 40)
|
|
|
|
release = Release.objects.create(version="abcdabc", organization=org)
|
|
release.add_project(project)
|
|
release.set_commits(
|
|
[
|
|
{
|
|
"id": "a" * 40,
|
|
"repository": repo.name,
|
|
"author_email": "[email protected]", # throw in an upper case letter
|
|
"author_name": "foo bar baz",
|
|
"message": "i fixed a bug",
|
|
},
|
|
{
|
|
"id": "b" * 40,
|
|
"repository": repo.name,
|
|
"author_email": "[email protected]",
|
|
"author_name": "foo bar baz",
|
|
"message": "i fixed another bug",
|
|
},
|
|
{
|
|
"id": "c" * 40,
|
|
"repository": repo.name,
|
|
"author_email": "[email protected]",
|
|
"author_name": "foo bar baz",
|
|
"message": "fixes %s" % (group.qualified_short_id),
|
|
},
|
|
]
|
|
)
|
|
|
|
author = CommitAuthor.objects.get(
|
|
name="foo bar baz", email="[email protected]", organization_id=org.id
|
|
)
|
|
|
|
commit_a = Commit.objects.get(repository_id=repo.id, organization_id=org.id, key="a" * 40)
|
|
assert commit_a
|
|
assert commit_a.message == "i fixed a bug"
|
|
assert commit_a.author_id == author.id
|
|
|
|
commit_c = Commit.objects.get(repository_id=repo.id, organization_id=org.id, key="c" * 40)
|
|
assert commit_c
|
|
assert commit_c.message is not None
|
|
assert "fixes" in commit_c.message
|
|
assert commit_c.author_id == author.id
|
|
|
|
# test that backfilling fills in missing message and author
|
|
commit = Commit.objects.get(id=commit.id)
|
|
assert commit.message == "i fixed another bug"
|
|
assert commit.author_id == author.id
|
|
|
|
assert ReleaseCommit.objects.filter(
|
|
commit__key="a" * 40, commit__repository_id=repo.id, release=release
|
|
).exists()
|
|
assert ReleaseCommit.objects.filter(
|
|
commit__key="b" * 40, commit__repository_id=repo.id, release=release
|
|
).exists()
|
|
assert ReleaseCommit.objects.filter(
|
|
commit__key="c" * 40, commit__repository_id=repo.id, release=release
|
|
).exists()
|
|
|
|
assert GroupLink.objects.filter(
|
|
group_id=group.id, linked_type=GroupLink.LinkedType.commit, linked_id=commit_c.id
|
|
).exists()
|
|
|
|
assert GroupResolution.objects.filter(group=group, release=release).exists()
|
|
assert (
|
|
GroupResolution.objects.get(group=group, release=release).status
|
|
== GroupResolution.Status.resolved
|
|
)
|
|
assert Group.objects.get(id=group.id).status == GroupStatus.RESOLVED
|
|
|
|
latest_commit = Commit.objects.get(repository_id=repo.id, key="a" * 40)
|
|
|
|
release = Release.objects.get(id=release.id)
|
|
assert release.commit_count == 3
|
|
assert release.authors == [str(author.id)]
|
|
assert release.last_commit_id == latest_commit.id
|
|
assert not GroupInbox.objects.filter(group=group).exists()
|
|
|
|
@freeze_time()
|
|
@receivers_raise_on_send()
|
|
def test_using_saved_data(self):
|
|
org = self.create_organization(owner=Factories.create_user())
|
|
project = self.create_project(organization=org, name="foo")
|
|
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
|
|
author = CommitAuthor.objects.create(
|
|
name="foo bar baz", email="[email protected]", organization_id=org.id
|
|
)
|
|
|
|
author.preload_users()
|
|
Commit.objects.create(
|
|
repository_id=repo.id,
|
|
organization_id=org.id,
|
|
key="b" * 40,
|
|
author=author,
|
|
date_added="2019-03-01 12:00:00",
|
|
message="fixed a thing",
|
|
)
|
|
|
|
release = Release.objects.create(version="abcdabc", organization=org)
|
|
release.add_project(project)
|
|
release.set_commits(
|
|
[
|
|
{"id": "a" * 40, "repository": repo.name},
|
|
{"id": "b" * 40, "repository": repo.name},
|
|
{"id": "c" * 40, "repository": repo.name},
|
|
]
|
|
)
|
|
|
|
date_format = "%Y-%m-%d %H:%M:%S"
|
|
assert Commit.objects.filter(
|
|
repository_id=repo.id, organization_id=org.id, key="a" * 40
|
|
).exists()
|
|
commit_c = Commit.objects.get(repository_id=repo.id, organization_id=org.id, key="c" * 40)
|
|
assert commit_c.date_added.strftime(date_format) == timezone.now().strftime(date_format)
|
|
assert commit_c.message is None
|
|
|
|
# Using the id/repository payload should retain existing data.
|
|
commit_b = Commit.objects.get(repository_id=repo.id, organization_id=org.id, key="b" * 40)
|
|
assert commit_b.message == "fixed a thing"
|
|
assert commit_b.date_added.strftime(date_format) == "2019-03-01 12:00:00"
|
|
|
|
latest_commit = Commit.objects.get(repository_id=repo.id, key="a" * 40)
|
|
|
|
release = Release.objects.get(id=release.id)
|
|
assert release.commit_count == 3
|
|
assert release.authors == [str(author.id)]
|
|
assert release.last_commit_id == latest_commit.id
|
|
|
|
@patch("sentry.models.Commit.update")
|
|
@freeze_time()
|
|
@receivers_raise_on_send()
|
|
def test_multiple_releases_only_updates_once(self, mock_update):
|
|
org = self.create_organization(owner=Factories.create_user())
|
|
project = self.create_project(organization=org, name="foo")
|
|
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
|
|
release = Release.objects.create(version="abcdabc", organization=org)
|
|
release.add_project(project)
|
|
|
|
release.set_commits([{"id": "b" * 40, "repository": repo.name, "message": "old message"}])
|
|
|
|
# Setting the exact same commits, shouldn't call update
|
|
release.set_commits([{"id": "b" * 40, "repository": repo.name, "message": "old message"}])
|
|
assert mock_update.call_count == 0
|
|
|
|
# Setting a different commit message, should call update
|
|
release.set_commits([{"id": "b" * 40, "repository": repo.name, "message": "new message"}])
|
|
assert mock_update.call_count == 1
|
|
|
|
@receivers_raise_on_send()
|
|
def test_resolution_support_full_featured(self):
|
|
org = self.create_organization(owner=self.user)
|
|
project = self.create_project(organization=org, name="foo")
|
|
group = self.create_group(project=project)
|
|
add_group_to_inbox(group, GroupInboxReason.MANUAL)
|
|
assert GroupInbox.objects.filter(group=group).exists()
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
author = CommitAuthor.objects.create(
|
|
organization_id=org.id, name="Foo Bar", email=self.user.email
|
|
)
|
|
author.preload_users()
|
|
commit = Commit.objects.create(
|
|
organization_id=org.id,
|
|
repository_id=repo.id,
|
|
message="fixes %s" % (group.qualified_short_id),
|
|
key="alksdflskdfjsldkfajsflkslk",
|
|
author=author,
|
|
)
|
|
|
|
old_release = self.create_release(project=project, version="pre-1.0")
|
|
|
|
resolution = GroupResolution.objects.create(
|
|
group=group,
|
|
release=old_release,
|
|
type=GroupResolution.Type.in_next_release,
|
|
status=GroupResolution.Status.pending,
|
|
)
|
|
|
|
release = self.create_release(project=project, version="abcdabc")
|
|
release.set_commits([{"id": commit.key, "repository": repo.name}])
|
|
|
|
assert GroupLink.objects.filter(
|
|
group_id=group.id, linked_type=GroupLink.LinkedType.commit, linked_id=commit.id
|
|
).exists()
|
|
|
|
# Pull the object from the DB again to test updated attributes
|
|
resolution = GroupResolution.objects.get(group=group)
|
|
assert resolution.status == GroupResolution.Status.resolved
|
|
assert resolution.release == release
|
|
assert resolution.type == GroupResolution.Type.in_release
|
|
assert resolution.actor_id == self.user.id
|
|
|
|
assert Group.objects.get(id=group.id).status == GroupStatus.RESOLVED
|
|
assert not GroupInbox.objects.filter(group=group).exists()
|
|
|
|
@receivers_raise_on_send()
|
|
def test_resolution_support_without_author(self):
|
|
org = self.create_organization(owner=Factories.create_user())
|
|
project = self.create_project(organization=org, name="foo")
|
|
group = self.create_group(project=project)
|
|
add_group_to_inbox(group, GroupInboxReason.MANUAL)
|
|
assert GroupInbox.objects.filter(group=group).exists()
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
commit = Commit.objects.create(
|
|
organization_id=org.id,
|
|
repository_id=repo.id,
|
|
message="fixes %s" % (group.qualified_short_id),
|
|
key="alksdflskdfjsldkfajsflkslk",
|
|
)
|
|
|
|
release = self.create_release(project=project, version="abcdabc")
|
|
release.set_commits([{"id": commit.key, "repository": repo.name}])
|
|
|
|
assert GroupLink.objects.filter(
|
|
group_id=group.id, linked_type=GroupLink.LinkedType.commit, linked_id=commit.id
|
|
).exists()
|
|
|
|
resolution = GroupResolution.objects.get(group=group)
|
|
assert resolution.status == GroupResolution.Status.resolved
|
|
assert resolution.release == release
|
|
assert resolution.type == GroupResolution.Type.in_release
|
|
assert resolution.actor_id is None
|
|
|
|
assert Group.objects.get(id=group.id).status == GroupStatus.RESOLVED
|
|
assert not GroupInbox.objects.filter(group=group).exists()
|
|
|
|
@patch("sentry.integrations.example.integration.ExampleIntegration.sync_status_outbound")
|
|
@receivers_raise_on_send()
|
|
def test_resolution_support_with_integration(self, mock_sync_status_outbound):
|
|
org = self.create_organization(owner=Factories.create_user())
|
|
integration = self.create_integration(
|
|
organization=org,
|
|
external_id="example:1",
|
|
provider="example",
|
|
name="Example",
|
|
oi_params={
|
|
"config": {
|
|
"sync_comments": True,
|
|
"sync_status_outbound": True,
|
|
"sync_status_inbound": True,
|
|
"sync_assignee_outbound": True,
|
|
"sync_assignee_inbound": True,
|
|
}
|
|
},
|
|
)
|
|
project = self.create_project(organization=org, name="foo")
|
|
group = self.create_group(project=project)
|
|
add_group_to_inbox(group, GroupInboxReason.MANUAL)
|
|
assert GroupInbox.objects.filter(group=group).exists()
|
|
external_issue = ExternalIssue.objects.get_or_create(
|
|
organization_id=org.id, integration_id=integration.id, key="APP-%s" % group.id
|
|
)[0]
|
|
|
|
GroupLink.objects.get_or_create(
|
|
group_id=group.id,
|
|
project_id=group.project_id,
|
|
linked_type=GroupLink.LinkedType.issue,
|
|
linked_id=external_issue.id,
|
|
relationship=GroupLink.Relationship.references,
|
|
)[0]
|
|
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
commit = Commit.objects.create(
|
|
organization_id=org.id,
|
|
repository_id=repo.id,
|
|
message="fixes %s" % (group.qualified_short_id),
|
|
key="alksdflskdfjsldkfajsflkslk",
|
|
)
|
|
|
|
release = self.create_release(project=project, version="abcdabc")
|
|
|
|
with self.tasks():
|
|
with self.feature({"organizations:integrations-issue-sync": True}):
|
|
release.set_commits([{"id": commit.key, "repository": repo.name}])
|
|
|
|
mock_sync_status_outbound.assert_called_once_with(external_issue, True, group.project_id)
|
|
|
|
assert GroupLink.objects.filter(
|
|
group_id=group.id, linked_type=GroupLink.LinkedType.commit, linked_id=commit.id
|
|
).exists()
|
|
|
|
resolution = GroupResolution.objects.get(group=group)
|
|
assert resolution.status == GroupResolution.Status.resolved
|
|
assert resolution.release == release
|
|
assert resolution.type == GroupResolution.Type.in_release
|
|
assert resolution.actor_id is None
|
|
|
|
assert Group.objects.get(id=group.id).status == GroupStatus.RESOLVED
|
|
assert not GroupInbox.objects.filter(group=group).exists()
|
|
|
|
@receivers_raise_on_send()
|
|
def test_long_email(self):
|
|
org = self.create_organization(owner=Factories.create_user())
|
|
project = self.create_project(organization=org, name="foo")
|
|
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
|
|
release = Release.objects.create(version="abcdabc", organization=org)
|
|
release.add_project(project)
|
|
commit_email = "a" * 248 + "@a.com" # 254 chars long, max valid email.
|
|
release.set_commits(
|
|
[
|
|
{
|
|
"id": "a" * 40,
|
|
"repository": repo.name,
|
|
"author_name": "foo bar baz",
|
|
"author_email": commit_email,
|
|
"message": "i fixed a bug",
|
|
}
|
|
]
|
|
)
|
|
commit = Commit.objects.get(repository_id=repo.id, organization_id=org.id, key="a" * 40)
|
|
assert commit.author.email == truncatechars(commit_email, 75)
|
|
|
|
|
|
@region_silo_test
|
|
class SetRefsTest(SetRefsTestCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.release = Release.objects.create(version="abcdabc", organization=self.org)
|
|
self.release.add_project(self.project)
|
|
|
|
@patch("sentry.tasks.commits.fetch_commits")
|
|
@receivers_raise_on_send()
|
|
def test_simple(self, mock_fetch_commit):
|
|
refs = [
|
|
{
|
|
"repository": "test/repo",
|
|
"previousCommit": "previous-commit-id",
|
|
"commit": "current-commit-id",
|
|
},
|
|
{
|
|
"repository": "test/repo",
|
|
"previousCommit": "previous-commit-id-2",
|
|
"commit": "current-commit-id-2",
|
|
},
|
|
]
|
|
|
|
self.release.set_refs(refs, self.user.id, True)
|
|
|
|
commits = Commit.objects.all().order_by("id")
|
|
self.assert_commit(commits[0], refs[0]["commit"])
|
|
self.assert_commit(commits[1], refs[1]["commit"])
|
|
|
|
head_commits = ReleaseHeadCommit.objects.all()
|
|
self.assert_head_commit(head_commits[0], refs[1]["commit"])
|
|
|
|
self.assert_fetch_commits(mock_fetch_commit, None, self.release.id, refs)
|
|
|
|
@patch("sentry.tasks.commits.fetch_commits")
|
|
@receivers_raise_on_send()
|
|
def test_invalid_repos(self, mock_fetch_commit):
|
|
refs = [
|
|
{
|
|
"repository": "unknown-repository-name",
|
|
"previousCommit": "previous-commit-id",
|
|
"commit": "current-commit-id",
|
|
},
|
|
{
|
|
"repository": "unknown-repository-name",
|
|
"previousCommit": "previous-commit-id-2",
|
|
"commit": "current-commit-id-2",
|
|
},
|
|
]
|
|
|
|
with pytest.raises(InvalidRepository):
|
|
self.release.set_refs(refs, self.user.id)
|
|
|
|
assert len(Commit.objects.all()) == 0
|
|
assert len(ReleaseHeadCommit.objects.all()) == 0
|
|
|
|
@patch("sentry.tasks.commits.fetch_commits")
|
|
@receivers_raise_on_send()
|
|
def test_handle_commit_ranges(self, mock_fetch_commit):
|
|
refs = [
|
|
{
|
|
"repository": "test/repo",
|
|
"previousCommit": None,
|
|
"commit": "previous-commit-id..current-commit-id",
|
|
},
|
|
{
|
|
"repository": "test/repo",
|
|
"previousCommit": "previous-commit-will-be-ignored",
|
|
"commit": "previous-commit-id-2..current-commit-id-2",
|
|
},
|
|
{"repository": "test/repo", "commit": "previous-commit-id-3..current-commit-id-3"},
|
|
]
|
|
|
|
self.release.set_refs(refs, self.user.id, True)
|
|
|
|
commits = Commit.objects.all().order_by("id")
|
|
self.assert_commit(commits[0], "current-commit-id")
|
|
self.assert_commit(commits[1], "current-commit-id-2")
|
|
self.assert_commit(commits[2], "current-commit-id-3")
|
|
|
|
head_commits = ReleaseHeadCommit.objects.all()
|
|
self.assert_head_commit(head_commits[0], "current-commit-id-3")
|
|
|
|
self.assert_fetch_commits(mock_fetch_commit, None, self.release.id, refs)
|
|
|
|
@patch("sentry.tasks.commits.fetch_commits")
|
|
@receivers_raise_on_send()
|
|
def test_fetch_false(self, mock_fetch_commit):
|
|
refs = [
|
|
{
|
|
"repository": "test/repo",
|
|
"previousCommit": "previous-commit-id",
|
|
"commit": "current-commit-id",
|
|
},
|
|
{
|
|
"repository": "test/repo",
|
|
"previousCommit": "previous-commit-id-2",
|
|
"commit": "current-commit-id-2",
|
|
},
|
|
]
|
|
|
|
self.release.set_refs(refs, self.user.id, False)
|
|
|
|
commits = Commit.objects.all().order_by("id")
|
|
self.assert_commit(commits[0], refs[0]["commit"])
|
|
self.assert_commit(commits[1], refs[1]["commit"])
|
|
|
|
head_commits = ReleaseHeadCommit.objects.all()
|
|
self.assert_head_commit(head_commits[0], refs[1]["commit"])
|
|
|
|
assert len(mock_fetch_commit.method_calls) == 0
|
|
|
|
def test_invalid_version_none_value(self):
|
|
assert not Release.is_valid_version(None)
|
|
|
|
def test_invalid_version(self):
|
|
cases = ["", "latest", ".", "..", "\t", "\n", " "]
|
|
|
|
for case in cases:
|
|
with pytest.raises(ValidationError):
|
|
Release.objects.create(version=case, organization=self.org)
|
|
|
|
with pytest.raises(ValidationError):
|
|
Release.objects.create(organization=self.org)
|
|
|
|
# @staticmethod
|
|
def test_invalid_chars_in_version(self):
|
|
version = (
|
|
"\n> [email protected] release:version\n> echo "
|
|
"'dev-19be1b7e-dirty'\n\ndev-19be1b7e-dirty"
|
|
)
|
|
assert not Release.is_valid_version(version)
|
|
|
|
version = "\t hello world"
|
|
assert not Release.is_valid_version(version)
|
|
|
|
version = "\f hello world again"
|
|
assert not Release.is_valid_version(version)
|
|
|
|
version = "/ helo"
|
|
assert not Release.is_valid_version(version)
|
|
|
|
version = "\r hello world again"
|
|
assert not Release.is_valid_version(version)
|
|
|
|
version = "\x0c dogs and rabbits"
|
|
assert not Release.is_valid_version(version)
|
|
|
|
version = "\\ hello world again"
|
|
assert not Release.is_valid_version(version)
|
|
|
|
|
|
@region_silo_test
|
|
class SemverReleaseParseTestCase(TestCase):
|
|
def setUp(self):
|
|
self.org = self.create_organization()
|
|
|
|
def test_parse_release_into_semver_cols(self):
|
|
"""
|
|
Test that ensures that release version is parsed into the semver cols on Release model
|
|
and that if build code can be parsed as a 64 bit integer then it is stored in build_number
|
|
"""
|
|
version = "[email protected]+20200101100"
|
|
release = Release.objects.create(organization=self.org, version=version)
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == "rc1"
|
|
assert release.build_code == "20200101100"
|
|
assert release.build_number == 20200101100
|
|
assert release.package == "org.example.FooApp"
|
|
|
|
def test_parse_release_into_semver_cols_using_custom_get_or_create(self):
|
|
"""
|
|
Test that ensures that release version is parsed into the semver cols on Release model
|
|
when using the custom `Release.get_or_create` method
|
|
"""
|
|
version = "[email protected]+20200101100"
|
|
project = self.create_project(organization=self.org, name="foo")
|
|
release = Release.get_or_create(project=project, version=version)
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == "rc1"
|
|
assert release.build_code == "20200101100"
|
|
assert release.build_number == 20200101100
|
|
assert release.package == "org.example.FooApp"
|
|
|
|
def test_parse_release_into_semver_cols_with_non_int_build_code(self):
|
|
"""
|
|
Test that ensures that if the build_code passed as part of the semver version cannot be
|
|
parsed as a 64 bit integer due to non int release then build number is left empty
|
|
"""
|
|
version = "[email protected]+whatever"
|
|
release = Release.objects.create(organization=self.org, version=version)
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == "rc1"
|
|
assert release.build_code == "whatever"
|
|
assert release.build_number is None
|
|
assert release.package == "org.example.FooApp"
|
|
|
|
def test_parse_release_into_semver_cols_with_int_build_code_gt_64_int(self):
|
|
"""
|
|
Test that ensures that if the build_code passed as part of the semver version cannot be
|
|
parsed as a 64 bit integer due to bigger than 64 bit integer then build number is left empty
|
|
"""
|
|
version = "[email protected]+202001011005464576758979789794566455464746"
|
|
release = Release.objects.create(organization=self.org, version=version)
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == "rc1"
|
|
assert release.build_code == "202001011005464576758979789794566455464746"
|
|
assert release.build_number is None
|
|
assert release.package == "org.example.FooApp"
|
|
|
|
def test_parse_release_into_semver_cols_with_negative_build_code(self):
|
|
"""
|
|
Test that ensures that if the build_code passed as part of the semver version can be
|
|
parsed as a 64 bit integer but has a negative sign then build number is left
|
|
empty
|
|
"""
|
|
version = "[email protected]+-2020"
|
|
release = Release.objects.create(organization=self.org, version=version)
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == "rc1"
|
|
assert release.build_code == "-2020"
|
|
assert release.build_number is None
|
|
assert release.package == "org.example.FooApp"
|
|
|
|
def test_parse_release_into_semver_cols_with_no_prerelease(self):
|
|
"""
|
|
Test that ensures that prerelease is stores as an empty string if not included
|
|
in the version.
|
|
"""
|
|
version = "[email protected]+whatever"
|
|
release = Release.objects.create(organization=self.org, version=version)
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == ""
|
|
assert release.build_code == "whatever"
|
|
assert release.build_number is None
|
|
assert release.package == "org.example.FooApp"
|
|
|
|
def test_parse_non_semver_should_not_fail(self):
|
|
"""
|
|
Test that ensures nothing breaks when sending a non semver compatible release
|
|
"""
|
|
version = "hello world"
|
|
release = Release.objects.create(organization=self.org, version=version)
|
|
assert release.version == "hello world"
|
|
|
|
def test_parse_release_overflow_bigint(self):
|
|
"""
|
|
Tests that we don't error if we have a version component that is larger than
|
|
a postgres bigint.
|
|
"""
|
|
version = "[email protected]+12345"
|
|
release = Release.objects.create(organization=self.org, version=version)
|
|
assert release.version == version
|
|
assert release.major is None
|
|
assert release.minor is None
|
|
assert release.patch is None
|
|
assert release.revision is None
|
|
assert release.prerelease is None
|
|
assert release.build_code is None
|
|
assert release.build_number is None
|
|
assert release.package is None
|
|
|
|
def test_parse_release_into_semver_cols_with_get_or_create(self):
|
|
"""
|
|
Test that ensures get_or_create populates semver fields
|
|
"""
|
|
version = "[email protected]+-2020"
|
|
release, _ = Release.objects.get_or_create(
|
|
organization=self.org, version=version, defaults={"status": ReleaseStatus.OPEN}
|
|
)
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == "rc1"
|
|
assert release.build_code == "-2020"
|
|
assert release.build_number is None
|
|
assert release.package == "org.example.FooApp"
|
|
|
|
def test_parse_release_into_semver_cols_on_pre_save(self):
|
|
"""
|
|
Test that ensures that calling save on a new Release instance parses version into semver
|
|
columns
|
|
"""
|
|
version = "[email protected]+-2020"
|
|
release = Release(organization=self.org, version=version)
|
|
release.save()
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == "rc1"
|
|
assert release.build_code == "-2020"
|
|
assert release.build_number is None
|
|
assert release.package == "org.example.FooApp"
|
|
|
|
def test_does_not_parse_release_into_semver_cols_on_pre_save_for_existing_release(self):
|
|
"""
|
|
Test that ensures that calling save on an existing Release instance does not re-parse
|
|
version into semver columns
|
|
"""
|
|
version = "[email protected]+-2020"
|
|
release = Release(organization=self.org, version=version)
|
|
release.save()
|
|
assert release.major == 1
|
|
assert release.minor == 0
|
|
assert release.patch == 0
|
|
assert release.revision == 0
|
|
assert release.prerelease == "rc1"
|
|
assert release.build_code == "-2020"
|
|
assert release.build_number is None
|
|
assert release.package == "org.example.FooApp"
|
|
release.version = "[email protected]+-1999"
|
|
release.save()
|
|
assert release.build_code == "-2020"
|
|
|
|
|
|
@region_silo_test
|
|
class ReleaseFilterBySemverTest(TestCase):
|
|
def test_invalid_query(self):
|
|
with pytest.raises(
|
|
InvalidSearchQuery,
|
|
match=INVALID_SEMVER_MESSAGE,
|
|
):
|
|
Release.objects.filter_by_semver(self.organization.id, parse_semver("1.2.hi", ">"))
|
|
|
|
def run_test(self, operator, version, expected_releases, organization_id=None, projects=None):
|
|
organization_id = organization_id if organization_id else self.organization.id
|
|
project_ids = [p.id for p in projects] if projects else None
|
|
assert set(
|
|
Release.objects.filter_by_semver(
|
|
organization_id, parse_semver(version, operator), project_ids=project_ids
|
|
)
|
|
) == set(expected_releases)
|
|
|
|
def test(self):
|
|
release = self.create_release(version="[email protected]")
|
|
release_2 = self.create_release(version="[email protected]")
|
|
self.run_test(">", "1.2.3", [release_2])
|
|
self.run_test(">=", "1.2.4", [release_2])
|
|
self.run_test("<", "1.2.4", [release])
|
|
self.run_test("<=", "1.2.3", [release])
|
|
self.run_test("!=", "1.2.3", [release_2])
|
|
|
|
def test_prerelease(self):
|
|
# Prerelease has weird sorting rules, where an empty string is higher priority
|
|
# than a non-empty string. Make sure this sorting works
|
|
release = self.create_release(version="[email protected]")
|
|
release_1 = self.create_release(version="[email protected]")
|
|
release_2 = self.create_release(version="[email protected]")
|
|
release_3 = self.create_release(version="[email protected]")
|
|
release_4 = self.create_release(version="[email protected]")
|
|
self.run_test(">=", "1.2.3", [release_2, release_3, release_4])
|
|
self.run_test(
|
|
">=",
|
|
"1.2.3-beta",
|
|
[release_1, release_2, release_3, release_4],
|
|
)
|
|
self.run_test("<", "1.2.3", [release_1, release])
|
|
|
|
def test_granularity(self):
|
|
self.create_release(version="[email protected]")
|
|
release_2 = self.create_release(version="[email protected]")
|
|
release_3 = self.create_release(version="[email protected]")
|
|
release_4 = self.create_release(version="[email protected]")
|
|
release_5 = self.create_release(version="[email protected]")
|
|
self.run_test(
|
|
">",
|
|
"1",
|
|
[release_2, release_3, release_4, release_5],
|
|
)
|
|
self.run_test(">", "1.2", [release_3, release_4, release_5])
|
|
self.run_test(">", "1.2.3", [release_4, release_5])
|
|
self.run_test(">", "1.2.3.4", [release_5])
|
|
self.run_test(">", "2", [])
|
|
|
|
def test_wildcard(self):
|
|
release_1 = self.create_release(version="[email protected]")
|
|
release_2 = self.create_release(version="[email protected]")
|
|
release_3 = self.create_release(version="[email protected]")
|
|
release_4 = self.create_release(version="[email protected]")
|
|
release_5 = self.create_release(version="[email protected]")
|
|
|
|
self.run_test(
|
|
"=",
|
|
"1.X",
|
|
[release_1, release_2, release_3, release_4],
|
|
)
|
|
self.run_test("=", "1.2.*", [release_2, release_3, release_4])
|
|
self.run_test("=", "1.2.3.*", [release_3, release_4])
|
|
self.run_test("=", "1.2.3.4", [release_4])
|
|
self.run_test("=", "2.*", [release_5])
|
|
|
|
def test_package(self):
|
|
release = self.create_release(version="[email protected]")
|
|
release_2 = self.create_release(version="[email protected]")
|
|
self.run_test(">=", "[email protected]", [release])
|
|
self.run_test(">=", "[email protected]", [release_2])
|
|
|
|
def test_project(self):
|
|
project_2 = self.create_project()
|
|
release = self.create_release(version="[email protected]")
|
|
release_2 = self.create_release(version="[email protected]")
|
|
release_3 = self.create_release(version="[email protected]", additional_projects=[project_2])
|
|
release_4 = self.create_release(version="[email protected]", project=project_2)
|
|
self.run_test(">=", "[email protected]", [release, release_2, release_3, release_4])
|
|
self.run_test(
|
|
">=",
|
|
"[email protected]",
|
|
[release, release_2, release_3, release_4],
|
|
projects=[self.project, project_2],
|
|
)
|
|
self.run_test(">=", "[email protected]", [release, release_2, release_3], projects=[self.project])
|
|
self.run_test(">=", "[email protected]", [release_3, release_4], projects=[project_2])
|
|
|
|
|
|
@region_silo_test
|
|
class ReleaseFilterBySemverBuildTest(TestCase):
|
|
def run_test(self, operator, build, expected_releases, organization_id=None, projects=None):
|
|
organization_id = organization_id if organization_id else self.organization.id
|
|
project_ids = [p.id for p in projects] if projects else None
|
|
assert set(
|
|
Release.objects.filter_by_semver_build(
|
|
organization_id, operator, build, project_ids=project_ids
|
|
)
|
|
) == set(expected_releases)
|
|
|
|
def test_no_build(self):
|
|
self.create_release(version="[email protected]")
|
|
self.create_release(version="[email protected]")
|
|
self.run_test("gt", "100", [])
|
|
self.run_test("exact", "105aab", [])
|
|
|
|
def test_numeric(self):
|
|
release_1 = self.create_release(version="[email protected]+123")
|
|
release_2 = self.create_release(version="[email protected]+456")
|
|
self.create_release(version="[email protected]+123abc")
|
|
self.run_test("gt", "123", [release_2])
|
|
self.run_test("lte", "123", [release_1])
|
|
self.run_test("exact", "123", [release_1])
|
|
|
|
def test_large_numeric(self):
|
|
release_1 = self.create_release(version="[email protected]+9223372036854775808")
|
|
self.create_release(version="[email protected]+9223372036854775809")
|
|
|
|
# This should only return `release_1`, since this exceeds the max size for a bigint and
|
|
# so should fall back to an exact string match instead.
|
|
self.run_test("gt", "9223372036854775808", [release_1])
|
|
|
|
def test_text(self):
|
|
release_1 = self.create_release(version="[email protected]+123")
|
|
release_2 = self.create_release(version="[email protected]+1234")
|
|
release_3 = self.create_release(version="[email protected]+123abc")
|
|
|
|
self.run_test("exact", "", [release_1, release_2, release_3])
|
|
self.run_test("exact", "*", [release_1, release_2, release_3])
|
|
self.run_test("exact", "123*", [release_1, release_2, release_3])
|
|
self.run_test("exact", "123a*", [release_3])
|
|
self.run_test("exact", "123ab", [])
|
|
self.run_test("exact", "123abc", [release_3])
|
|
|
|
|
|
@region_silo_test
|
|
class FollowsSemverVersioningSchemeTestCase(TestCase):
|
|
def setUp(self):
|
|
self.org = self.create_organization()
|
|
self.fake_package = "_fake_package_prj_"
|
|
|
|
# Project with 10 semver releases
|
|
self.proj_1 = self.create_project(organization=self.org)
|
|
for i in range(10):
|
|
self.create_release(version=f"[email protected].{i}", project=self.proj_1)
|
|
|
|
def test_follows_semver_with_all_releases_semver_and_semver_release_version(self):
|
|
"""
|
|
Test that ensures that when the last 10 releases and the release version passed in as an arg
|
|
follow semver versioning, then True should be returned
|
|
"""
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id, project_id=self.proj_1.id, release_version="[email protected]"
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_follows_semver_all_releases_semver_and_missing_package_semver_release_version(self):
|
|
"""
|
|
Test that ensures that even if a project is following semver, then if the release_version
|
|
supplied lacks a package, then for that specific release we opt the project out of being
|
|
considered a semver project
|
|
"""
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id, project_id=self.proj_1.id, release_version="2.0.0"
|
|
)
|
|
is False
|
|
)
|
|
|
|
def test_follows_semver_with_all_releases_semver_and_no_release_version(self):
|
|
"""
|
|
Test that ensures that when the last 10 releases follow semver versioning and no release
|
|
version is passed in as an argument, then True should be returned
|
|
"""
|
|
assert (
|
|
follows_semver_versioning_scheme(org_id=self.org.id, project_id=self.proj_1.id) is True
|
|
)
|
|
|
|
def test_follows_semver_with_all_releases_semver_and_non_semver_release_version(self):
|
|
"""
|
|
Test that ensures that even if the last 10 releases follow semver but the passed in
|
|
release_version doesn't then we should return False because we should not follow semver
|
|
versioning in this case
|
|
"""
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id, project_id=self.proj_1.id, release_version="fizbuzz"
|
|
)
|
|
is False
|
|
)
|
|
|
|
def test_follows_semver_user_accidentally_stopped_using_semver_a_few_times(self):
|
|
"""
|
|
Test that ensures that when a user accidentally stops using semver versioning for a few
|
|
times but there exists at least one semver compliant release in the last 3 releases and
|
|
at least 3 releases that are semver compliant in the last 10 then we still consider
|
|
project to be following semantic versioning
|
|
"""
|
|
proj = self.create_project(organization=self.org)
|
|
|
|
for i in range(2):
|
|
self.create_release(version=f"{self.fake_package}{proj.id}@1.{i}", project=proj)
|
|
for i in range(7):
|
|
self.create_release(version=f"foo release {i}", project=proj)
|
|
self.create_release(version=f"{self.fake_package}{proj.id}@1.9", project=proj)
|
|
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id,
|
|
project_id=proj.id,
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_follows_semver_user_stops_using_semver(self):
|
|
"""
|
|
Test that ensures that if a user stops using semver and so the last 3 releases in the last
|
|
10 releases are all non-semver releases, then the project does not follow semver anymore
|
|
since 1st condition of at least one semver release in the last 3 has to be a semver
|
|
release is not satisfied
|
|
"""
|
|
proj = self.create_project(organization=self.org)
|
|
|
|
for i in range(7):
|
|
self.create_release(version=f"{self.fake_package}{proj.id}@1.{i}", project=proj)
|
|
for i in range(3):
|
|
self.create_release(version=f"helloworld {i}", project=proj)
|
|
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id,
|
|
project_id=proj.id,
|
|
)
|
|
is False
|
|
)
|
|
|
|
def test_follows_semver_user_accidentally_uses_semver_a_few_times(self):
|
|
"""
|
|
Test that ensures that if user accidentally uses semver compliant versions for a few
|
|
times then the project will not be considered to be using semver
|
|
"""
|
|
proj = self.create_project(organization=self.org)
|
|
|
|
for i in range(8):
|
|
self.create_release(version=f"foo release {i}", project=proj)
|
|
for i in range(2):
|
|
self.create_release(version=f"{self.fake_package}{proj.id}@1.{i}", project=proj)
|
|
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id,
|
|
project_id=proj.id,
|
|
)
|
|
is False
|
|
)
|
|
|
|
def test_follows_semver_user_starts_using_semver(self):
|
|
"""
|
|
Test that ensures if a user starts using semver by having at least the last 3 releases
|
|
using semver then we consider the project to be using semver
|
|
"""
|
|
proj = self.create_project(organization=self.org)
|
|
|
|
for i in range(7):
|
|
self.create_release(version=f"foo release {i}", project=proj)
|
|
for i in range(3):
|
|
self.create_release(version=f"{self.fake_package}{proj.id}@1.{i}", project=proj)
|
|
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id,
|
|
project_id=proj.id,
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_follows_semver_user_starts_using_semver_with_less_than_10_recent_releases(self):
|
|
"""
|
|
Test that ensures that a project with only 5 (<10) releases and at least one semver
|
|
release in the most recent releases is considered to be following semver
|
|
"""
|
|
proj = self.create_project(organization=self.org)
|
|
|
|
for i in range(4):
|
|
self.create_release(version=f"helloworld {i}", project=proj)
|
|
self.create_release(version=f"{self.fake_package}{proj.id}@1.0", project=proj)
|
|
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id,
|
|
project_id=proj.id,
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_follows_semver_check_when_project_only_has_two_releases(self):
|
|
"""
|
|
Test that ensures that when a project has only two releases, then we consider project to
|
|
be semver or not based on if the most recent release follows semver or not
|
|
"""
|
|
# Case: User just started using semver
|
|
proj = self.create_project(organization=self.org)
|
|
self.create_release(version="helloworld 0", project=proj)
|
|
self.create_release(version=f"{self.fake_package}{proj.id}@1.0", project=proj)
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id,
|
|
project_id=proj.id,
|
|
)
|
|
is True
|
|
)
|
|
|
|
# Case: User just stopped using semver
|
|
proj_2 = self.create_project(organization=self.org)
|
|
self.create_release(version=f"{self.fake_package}{proj_2.id}@1.0", project=proj_2)
|
|
self.create_release(version="helloworld 1", project=proj_2)
|
|
assert (
|
|
follows_semver_versioning_scheme(
|
|
org_id=self.org.id,
|
|
project_id=proj_2.id,
|
|
)
|
|
is False
|
|
)
|
|
|
|
|
|
@region_silo_test
|
|
class ClearCommitsTestCase(TestCase):
|
|
@receivers_raise_on_send()
|
|
def test_simple(self):
|
|
org = self.create_organization(owner=Factories.create_user())
|
|
project = self.create_project(organization=org, name="foo")
|
|
group = self.create_group(project=project)
|
|
|
|
repo = Repository.objects.create(organization_id=org.id, name="test/repo")
|
|
|
|
author = CommitAuthor.objects.create(
|
|
name="foo bar baz", email="[email protected]", organization_id=org.id
|
|
)
|
|
|
|
author2 = CommitAuthor.objects.create(
|
|
name="foo bar boo", email="[email protected]", organization_id=org.id
|
|
)
|
|
|
|
author.preload_users()
|
|
author2.preload_users()
|
|
commit = Commit.objects.create(
|
|
organization_id=org.id,
|
|
repository_id=repo.id,
|
|
author=author,
|
|
date_added="2019-03-01 12:00:00",
|
|
message="fixes %s" % (group.qualified_short_id),
|
|
key="alksdflskdfjsldkfajsflkslk",
|
|
)
|
|
commit2 = Commit.objects.create(
|
|
organization_id=org.id,
|
|
repository_id=repo.id,
|
|
author=author2,
|
|
date_added="2019-03-01 12:02:00",
|
|
message="i fixed something",
|
|
key="lskfslknsdkcsnlkdflksfdkls",
|
|
)
|
|
|
|
release = Release.objects.create(version="abcdabc", organization=org)
|
|
release.add_project(project)
|
|
release.set_commits(
|
|
[
|
|
{"id": commit.key, "repository": repo.name},
|
|
{"id": commit2.key, "repository": repo.name},
|
|
]
|
|
)
|
|
# Confirm setup works
|
|
assert ReleaseCommit.objects.filter(commit=commit, release=release).exists()
|
|
assert ReleaseCommit.objects.filter(commit=commit2, release=release).exists()
|
|
|
|
assert release.commit_count == 2
|
|
assert release.authors == [str(author.id), str(author2.id)]
|
|
assert release.last_commit_id == commit.id
|
|
|
|
assert ReleaseHeadCommit.objects.filter(
|
|
release_id=release.id, commit_id=commit.id, repository_id=repo.id
|
|
).exists()
|
|
|
|
# Now clear the release;
|
|
release.clear_commits()
|
|
assert not ReleaseCommit.objects.filter(commit=commit, release=release).exists()
|
|
assert not ReleaseCommit.objects.filter(commit=commit2, release=release).exists()
|
|
assert not ReleaseHeadCommit.objects.filter(
|
|
release_id=release.id, commit_id=commit.id, repository_id=repo.id
|
|
).exists()
|
|
|
|
assert release.commit_count == 0
|
|
assert release.authors == []
|
|
assert not release.last_commit_id
|
|
|
|
# Commits should still exist
|
|
assert Commit.objects.filter(
|
|
id=commit.id, organization_id=org.id, repository_id=repo.id
|
|
).exists()
|
|
assert Commit.objects.filter(
|
|
id=commit2.id, organization_id=org.id, repository_id=repo.id
|
|
).exists()
|
|
|
|
|
|
@region_silo_test
|
|
class ReleaseProjectManagerTestCase(TransactionTestCase):
|
|
def test_custom_manager(self):
|
|
self.assertIsInstance(ReleaseProject.objects, ReleaseProjectModelManager)
|
|
|
|
@receivers_raise_on_send()
|
|
def test_post_save_signal_runs_if_dynamic_sampling_is_disabled(self):
|
|
project = self.create_project(name="foo")
|
|
release = Release.objects.create(organization_id=project.organization_id, version="42")
|
|
|
|
with patch("sentry.models.release.schedule_invalidate_project_config") as mock_task:
|
|
release.add_project(project)
|
|
assert mock_task.mock_calls == []
|
|
|
|
@receivers_raise_on_send()
|
|
def test_post_save_signal_runs_if_dynamic_sampling_is_enabled_and_latest_release_rule_does_not_exist(
|
|
self,
|
|
):
|
|
with Feature(
|
|
{
|
|
"organizations:dynamic-sampling": True,
|
|
}
|
|
):
|
|
project = self.create_project(name="foo")
|
|
release = Release.objects.create(organization_id=project.organization_id, version="42")
|
|
|
|
with patch("sentry.models.release.schedule_invalidate_project_config") as mock_task:
|
|
release.add_project(project)
|
|
assert mock_task.mock_calls == []
|
|
|
|
@receivers_raise_on_send()
|
|
def test_post_save_signal_runs_if_dynamic_sampling_is_enabled_and_latest_release_rule_exists(
|
|
self,
|
|
):
|
|
with Feature(
|
|
{
|
|
"organizations:dynamic-sampling": True,
|
|
}
|
|
):
|
|
project = self.create_project(name="foo")
|
|
release = Release.objects.create(organization_id=project.organization_id, version="42")
|
|
project_boosted_releases = ProjectBoostedReleases(project.id)
|
|
# We store a boosted release for this project.
|
|
project_boosted_releases.add_boosted_release(release.id, None)
|
|
assert project_boosted_releases.has_boosted_releases
|
|
|
|
with patch("sentry.models.release.schedule_invalidate_project_config") as mock_task:
|
|
release.add_project(project)
|
|
assert mock_task.mock_calls == [
|
|
mock_call(project_id=project.id, trigger="releaseproject.post_save")
|
|
]
|