def test_get_owners_when_codeowners_and_issueowners_exists(self): self.team = self.create_team( organization=self.organization, slug="tiger-team", members=[self.user] ) self.team2 = self.create_team( organization=self.organization, slug="dolphin-team", members=[self.user] ) self.project = self.create_project( organization=self.organization, teams=[self.team, self.team2] ) self.code_mapping = self.create_code_mapping(project=self.project) rule_a = Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]) rule_b = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) rule_c = Rule(Matcher("path", "*.py"), [Owner("team", self.team2.slug)]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), fallthrough=True ) self.create_codeowners( self.project, self.code_mapping, raw="*.py @tiger-team", schema=dump_schema([rule_c]) ) self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "api/foo.py"}]}} ), ( [ActorTuple(self.team.id, Team), ActorTuple(self.team2.id, Team)], [rule_a, rule_c], ), )
def test_get_autoassign_owners_when_codeowners_and_issueowners_exists( self): self.team = self.create_team(organization=self.organization, slug="tiger-team", members=[self.user]) self.team2 = self.create_team(organization=self.organization, slug="dolphin-team", members=[self.user]) self.project = self.create_project(organization=self.organization, teams=[self.team, self.team2]) self.code_mapping = self.create_code_mapping(project=self.project) rule_a = Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]) rule_b = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) rule_c = Rule(Matcher("path", "*.py"), [Owner("team", self.team2.slug)]) ProjectOwnership.objects.create(project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), fallthrough=True) self.create_codeowners(self.project, self.code_mapping, raw="*.py @tiger-team", schema=dump_schema([rule_c])) # No autoassignment on match assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "api/foo.py" }] }}) == (False, [self.team, self.team2], False) # autoassignment is True owner = ProjectOwnership.objects.get(project_id=self.project.id) owner.auto_assignment = True owner.save() assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "api/foo.py" }] }}) == (True, [self.team, self.team2], False) # # more than 2 matches assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "src/foo.py" }] }}) == (True, [self.user, self.team], False)
def test_get_autoassign_owners_only_issueowners_exists(self): rule_a = Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]) rule_b = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), ) # No data matches assert ProjectOwnership.get_autoassign_owners(self.project.id, {}) == (False, [], False) # No autoassignment on match assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "foo.py" }] }}) == (False, [self.team], False) # autoassignment is True owner = ProjectOwnership.objects.get(project_id=self.project.id) owner.auto_assignment = True owner.save() assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "foo.py" }] }}) == (True, [self.team], False)
def test_get_owners_when_codeowners_exists_and_no_issueowners(self): # This case will never exist bc we create a ProjectOwnership record if none exists when creating a ProjectCodeOwner record. # We have this testcase for potential corrupt data. self.team = self.create_team(organization=self.organization, slug="tiger-team", members=[self.user]) self.code_mapping = self.create_code_mapping(project=self.project) rule_a = Rule(Matcher("path", "*.js"), [Owner("team", self.team.slug)]) self.create_codeowners( self.project, self.code_mapping, raw="*.js @tiger-team", schema=dump_schema([rule_a]), ) self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "src/foo.js" }] }}), ( [ActorTuple(self.team.id, Team)], [rule_a], ), )
def validate_raw(self, attrs, source): if not attrs[source].strip(): return attrs try: rules = parse_rules(attrs[source]) except ParseError as e: raise serializers.ValidationError( u'Parse error: %r (line %d, column %d)' % (e.expr.name, e.line(), e.column())) schema = dump_schema(rules) bad_actors = [] for rule in rules: for owner in rule.owners: try: resolve_actor(owner, self.context['ownership'].project_id) except UnknownActor: if owner.type == 'user': bad_actors.append(owner.identifier) if owner.type == 'team': bad_actors.append(u'#{}'.format(owner.identifier)) if bad_actors: raise serializers.ValidationError( u'Invalid rule owners: {}'.format(", ".join(bad_actors))) attrs['schema'] = schema return attrs
def test_multiple_owners_order_matters(self): users = [self.user, self.user2, self.user3] rules = [ Rule(Matcher("path", "*.py"), [Owner("user", users[0].email)]), Rule(Matcher("path", "*foo*"), [Owner("user", users[1].email)]), Rule(Matcher("path", "*"), [Owner("user", users[2].email)]), ] rules.reverse() ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema(rules), fallthrough=True ) event1 = self.store_event( data={"stacktrace": {"frames": [{"filename": "foo.py"}]}}, project_id=self.project.id ) self.path = reverse( "sentry-api-0-event-owners", kwargs={ "organization_slug": self.organization.slug, "project_slug": self.project.slug, "event_id": event1.event_id, }, ) resp = self.client.get(self.path) assert resp.status_code == 200 assert len(resp.data["owners"]) == 3 assert [o["id"] for o in resp.data["owners"]] == [str(u.id) for u in reversed(users)] assert resp.data["rule"] == Matcher("path", "*") assert len(resp.data["rules"]) == 3
def validate_raw(self, attrs, source): if not attrs[source].strip(): return attrs try: rules = parse_rules(attrs[source]) except ParseError as e: raise serializers.ValidationError( u'Parse error: %r (line %d, column %d)' % ( e.expr.name, e.line(), e.column() )) schema = dump_schema(rules) owners = {o for rule in rules for o in rule.owners} actors = resolve_actors(owners, self.context['ownership'].project_id) bad_actors = [] for owner, actor in six.iteritems(actors): if actor is None: if owner.type == 'user': bad_actors.append(owner.identifier) elif owner.type == 'team': bad_actors.append(u'#{}'.format(owner.identifier)) if bad_actors: raise serializers.ValidationError( u'Invalid rule owners: {}'.format(", ".join(bad_actors)) ) attrs['schema'] = schema return attrs
def test_one_owner(self): rule_a = Rule(Matcher("path", "*.py"), [Owner("user", self.user.email)]) ProjectOwnership.objects.create(project_id=self.project.id, schema=dump_schema([rule_a]), fallthrough=True) event1 = self.store_event( data={"stacktrace": { "frames": [{ "filename": "foo.py" }] }}, project_id=self.project.id) self.path = reverse( "sentry-api-0-event-owners", kwargs={ "organization_slug": self.organization.slug, "project_slug": self.project.slug, "event_id": event1.event_id, }, ) resp = self.client.get(self.path) assert resp.status_code == 200 assert len(resp.data["owners"]) == 1 assert resp.data["owners"][0]["id"] == six.text_type(self.user.id) assert resp.data["rule"] == Matcher("path", "*.py") assert len(resp.data["rules"]) == 1
def test_matching_non_existing_owner(self): rule_a = Rule(Matcher("path", "*"), [Owner("user", "*****@*****.**")]) ProjectOwnership.objects.create(project_id=self.project.id, schema=dump_schema([rule_a]), fallthrough=True) event1 = self.store_event( data={"stacktrace": { "frames": [{ "filename": "foo.py" }] }}, project_id=self.project.id) self.path = reverse( "sentry-api-0-event-owners", kwargs={ "organization_slug": self.organization.slug, "project_slug": self.project.slug, "event_id": event1.event_id, }, ) resp = self.client.get(self.path) assert resp.status_code == 200 assert len(resp.data["owners"]) == 0 assert resp.data["rule"] == Matcher(type="path", pattern="*") assert len(resp.data["rules"]) == 1
def validate(self, attrs): if not attrs.get("raw", "").strip(): return attrs try: rules = parse_rules(attrs["raw"]) except ParseError as e: raise serializers.ValidationError({ "raw": "Parse error: %r (line %d, column %d)" % (e.expr.name, e.line(), e.column()) }) schema = dump_schema(rules) owners = {o for rule in rules for o in rule.owners} actors = resolve_actors(owners, self.context["ownership"].project_id) bad_actors = [] for owner, actor in actors.items(): if actor is None: if owner.type == "user": bad_actors.append(owner.identifier) elif owner.type == "team": bad_actors.append(f"#{owner.identifier}") if bad_actors: bad_actors.sort() raise serializers.ValidationError({ "raw": "Invalid rule owners: {}".format(", ".join(bad_actors)) }) attrs["schema"] = schema return attrs
def setUp(self): self.user2 = self.create_user(email="*****@*****.**", is_active=True) self.user3 = self.create_user(email="*****@*****.**", is_active=True) self.team2 = self.create_team(organization=self.organization, members=[self.user, self.user2]) self.project.add_team(self.team2) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ grammar.Rule(Matcher("path", "*.py"), [Owner("team", self.team2.slug)]), grammar.Rule(Matcher("path", "*.jsx"), [Owner("user", self.user.email)]), grammar.Rule(Matcher("path", "*.jx"), [Owner("user", self.user3.email)]), grammar.Rule( Matcher("path", "*.cbl"), [ Owner("user", user.email) for user in User.objects.filter( id__in=self.project.member_set.values_list( "user", flat=True)) ], ), grammar.Rule(Matcher("path", "*.lol"), []), ]), fallthrough=True, )
def test_owners_of_different_types_ordered_correctly(self): owners = [self.user, self.team3, self.user2, self.team2, self.user3, self.team] rules = [ Rule(Matcher("path", "*.py"), [Owner("user", owners[0].email)]), Rule(Matcher("path", "*py"), [Owner("team", owners[1].slug)]), Rule(Matcher("path", "*foo*"), [Owner("user", owners[2].email)]), Rule(Matcher("path", "*y"), [Owner("team", owners[3].slug)]), Rule(Matcher("path", "*"), [Owner("user", owners[4].email)]), Rule(Matcher("path", "*o.py"), [Owner("team", owners[5].slug)]), ] ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema(rules), fallthrough=True ) event1 = self.store_event( data={"stacktrace": {"frames": [{"filename": "foo.py"}]}}, project_id=self.project.id ) self.path = reverse( "sentry-api-0-event-owners", kwargs={ "organization_slug": self.organization.slug, "project_slug": self.project.slug, "event_id": event1.event_id, }, ) resp = self.client.get(self.path) assert resp.status_code == 200 assert len(resp.data["owners"]) == 6 assert [o["id"] for o in resp.data["owners"]] == [str(o.id) for o in owners] assert [o["type"] for o in resp.data["owners"]] == ["user", "team"] * 3 assert resp.data["rule"] == Matcher("path", "*.py") assert len(resp.data["rules"]) == 6
def test_get_autoassign_owners_only_codeowners_exists(self): # This case will never exist bc we create a ProjectOwnership record if none exists when creating a ProjectCodeOwner record. # We have this testcase for potential corrupt data. self.team = self.create_team(organization=self.organization, slug="tiger-team", members=[self.user]) self.code_mapping = self.create_code_mapping(project=self.project) rule_a = Rule(Matcher("path", "*.js"), [Owner("team", self.team.slug)]) self.create_codeowners( self.project, self.code_mapping, raw="*.js @tiger-team", schema=dump_schema([rule_a]), ) # No data matches assert ProjectOwnership.get_autoassign_owners(self.project.id, {}) == (False, [], False) # No autoassignment on match assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "foo.js" }] }}) == (False, [self.team], True)
def validate(self, attrs): if not attrs.get('raw', '').strip(): return attrs try: rules = parse_rules(attrs['raw']) except ParseError as e: raise serializers.ValidationError({ 'raw': u'Parse error: %r (line %d, column %d)' % (e.expr.name, e.line(), e.column()) }) schema = dump_schema(rules) owners = {o for rule in rules for o in rule.owners} actors = resolve_actors(owners, self.context['ownership'].project_id) bad_actors = [] for owner, actor in six.iteritems(actors): if actor is None: if owner.type == 'user': bad_actors.append(owner.identifier) elif owner.type == 'team': bad_actors.append(u'#{}'.format(owner.identifier)) if bad_actors: raise serializers.ValidationError({ 'raw': u'Invalid rule owners: {}'.format(", ".join(bad_actors)) }) attrs['schema'] = schema return attrs
def setUp(self): self.user = self.create_user(email="*****@*****.**", is_active=True) self.user2 = self.create_user(email="*****@*****.**", is_active=True) self.organization = self.create_organization(owner=self.user) self.team = self.create_team(organization=self.organization) self.project = self.create_project(name="Test", teams=[self.team]) OrganizationMemberTeam.objects.create( organizationmember=OrganizationMember.objects.get( user=self.user, organization=self.organization), team=self.team, ) self.create_member(user=self.user2, organization=self.organization, teams=[self.team]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ grammar.Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]), grammar.Rule(Matcher("path", "*.jx"), [Owner("user", self.user2.email)]), grammar.Rule( Matcher("path", "*.cbl"), [ Owner("user", self.user.email), Owner("user", self.user2.email) ], ), ]), fallthrough=True, )
def test_notify_users_with_owners(self): user = self.create_user(email="*****@*****.**", is_active=True) user2 = self.create_user(email="*****@*****.**", is_active=True) organization = self.create_organization(owner=user) team = self.create_team(organization=organization) project = self.create_project(name="Test", teams=[team]) OrganizationMemberTeam.objects.create( organizationmember=OrganizationMember.objects.get(user=user, organization=organization), team=team, ) self.create_member(user=user2, organization=organization, teams=[team]) self.group = self.create_group( first_seen=timezone.now(), last_seen=timezone.now(), project=project, message="hello world", logger="root", ) ProjectOwnership.objects.create( project_id=project.id, schema=dump_schema( [ grammar.Rule(Matcher("path", "*.py"), [Owner("team", team.slug)]), grammar.Rule(Matcher("path", "*.jx"), [Owner("user", user2.email)]), grammar.Rule( Matcher("path", "*.cbl"), [Owner("user", user.email), Owner("user", user2.email)], ), ] ), fallthrough=True, ) event_all_users = self.store_event( data=self.make_event_data("foo.cbl"), project_id=project.id ) self.assert_notify(event_all_users, [user.email, user2.email]) event_team = self.store_event(data=self.make_event_data("foo.py"), project_id=project.id) self.assert_notify(event_team, [user.email, user2.email]) event_single_user = self.store_event( data=self.make_event_data("foo.jx"), project_id=project.id ) self.assert_notify(event_single_user, [user2.email]) # Make sure that disabling mail alerts works as expected NotificationSetting.objects.update_settings( ExternalProviders.EMAIL, NotificationSettingTypes.ISSUE_ALERTS, NotificationSettingOptionValues.NEVER, user=user2, project=project, ) event_all_users = self.store_event( data=self.make_event_data("foo.cbl"), project_id=project.id ) self.assert_notify(event_all_users, [user.email])
def create_ownership( self, project: Project, rules: Optional[Sequence[Rule]] = None, fallthrough: bool = False ) -> ProjectOwnership: return ProjectOwnership.objects.create( project_id=project.id, schema=dump_schema(rules if rules else []), fallthrough=fallthrough, )
def test_owner_assignment_when_owners_have_been_unassigned(self): """ Test that ensures that if certain assignees get unassigned, and project rules are changed then the new group assignees should be re-calculated and re-assigned """ # Create rules and check assignees self.make_ownership() event = self.store_event( data={ "message": "oh no", "platform": "python", "stacktrace": { "frames": [{ "filename": "src/app/example.py" }] }, }, project_id=self.project.id, ) cache_key = write_event_to_cache(event) post_process_group( is_new=False, is_regression=False, is_new_group_environment=False, cache_key=cache_key, group_id=event.group_id, ) assignee = event.group.assignee_set.first() assert assignee.user == self.user user_3 = self.create_user() self.create_team_membership(self.team, user=user_3) # De-assign group assignees GroupAssignee.objects.deassign(event.group, self.user) assert event.group.assignee_set.first() is None # Change ProjectOwnership rules rules = [ Rule(Matcher("path", "src/*"), [Owner("user", user_3.email)]), ] self.prj_ownership.schema = dump_schema(rules) self.prj_ownership.save() cache_key = write_event_to_cache(event) post_process_group( is_new=False, is_regression=False, is_new_group_environment=False, cache_key=cache_key, group_id=event.group_id, ) # Group should be re-assigned to the new group owner assignee = event.group.assignee_set.first() assert assignee.user == user_3
def test_dump_schema(): assert dump_schema([Rule(Matcher("path", "*.js"), [Owner("team", "frontend")])]) == { "$version": 1, "rules": [ { "matcher": {"type": "path", "pattern": "*.js"}, "owners": [{"type": "team", "identifier": "frontend"}], } ], }
def make_ownership(self): rule_a = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) rule_b = Rule(Matcher("path", "tests/*"), [Owner("team", self.team.name)]) rule_c = Rule(Matcher("path", "src/app/*"), [Owner("team", self.team.name)]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b, rule_c]), fallthrough=True, auto_assignment=True, )
def test_get_owners_basic(self): rule_a = Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]) rule_b = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), fallthrough=True ) # No data matches assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) # Match only rule_a self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "foo.py"}]}} ), ([Actor(self.team.id, Team)], [rule_a]), ) # Match only rule_b self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "src/thing.txt"}]}} ), ([Actor(self.user.id, User)], [rule_b]), ) # Matches both rule_a and rule_b self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "src/foo.py"}]}} ), ([Actor(self.team.id, Team), Actor(self.user.id, User)], [rule_a, rule_b]), ) assert ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "xxxx"}]}} ) == (ProjectOwnership.Everyone, None) # When fallthrough = False, we don't implicitly assign to Everyone owner = ProjectOwnership.objects.get(project_id=self.project.id) owner.fallthrough = False owner.save() assert ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "xxxx"}]}} ) == ([], None) self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "src/foo.py"}]}} ), ([Actor(self.team.id, Team), Actor(self.user.id, User)], [rule_a, rule_b]), )
def validate_raw(self, attrs, source): if not attrs[source].strip(): return attrs try: rules = parse_rules(attrs[source]) except ParseError as e: raise serializers.ValidationError( u'Parse error: %r (line %d, column %d)' % ( e.expr.name, e.line(), e.column() )) attrs['schema'] = dump_schema(rules) return attrs
def test_get_owners_basic(self): matcher = Matcher('path', '*.py') ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ Rule(matcher, [ Owner('user', self.user.email), Owner('team', self.team.slug), ]), ]), fallthrough=True, ) # No data matches assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'foo.py', }] } } ) == ([Actor(self.user.id, User), Actor(self.team.id, Team)], matcher) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } } ) == (ProjectOwnership.Everyone, None) # When fallthrough = False, we don't implicitly assign to Everyone ProjectOwnership.objects.filter( project_id=self.project.id, ).update(fallthrough=False) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } } ) == ([], None)
def test_abs_path_when_filename_present(self): frame = { "filename": "computer.cpp", "abs_path": "C:\\My\\Path\\computer.cpp", } rule = Rule(Matcher("path", "*My\\Path*"), [Owner("team", self.team.slug)]) ProjectOwnership.objects.create(project_id=self.project.id, schema=dump_schema([rule]), fallthrough=True) assert ProjectOwnership.get_owners( self.project.id, {"stacktrace": { "frames": [frame] }}) == ([ActorTuple(self.team.id, Team)], [rule])
def make_ownership(self, extra_rules=None): rules = [ Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]), Rule(Matcher("path", "tests/*"), [Owner("team", self.team.name)]), Rule(Matcher("path", "src/app/*"), [Owner("team", self.team.name)]), ] if extra_rules: rules.extend(extra_rules) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema(rules), fallthrough=True, auto_assignment=True, )
def test_dump_schema(): assert dump_schema( [Rule(Matcher('path', '*.js'), [Owner('team', 'frontend')])]) == { '$version': 1, 'rules': [{ 'matcher': { 'type': 'path', 'pattern': '*.js', }, 'owners': [{ 'type': 'team', 'identifier': 'frontend', }] }] }
def test_get_owners_basic(self): matcher = Matcher('path', '*.py') ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ Rule(matcher, [ Owner('user', self.user.email), Owner('team', self.team.slug), ]), ]), fallthrough=True, ) # No data matches assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) assert ProjectOwnership.get_owners(self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'foo.py', }] } }) == ([Actor(self.user.id, User), Actor(self.team.id, Team)], matcher) assert ProjectOwnership.get_owners(self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } }) == (ProjectOwnership.Everyone, None) # When fallthrough = False, we don't implicitly assign to Everyone ProjectOwnership.objects.filter( project_id=self.project.id, ).update(fallthrough=False) assert ProjectOwnership.get_owners(self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } }) == ([], None)
def make_ownership(self): rule_a = Rule(Matcher('path', 'src/*'), [ Owner('user', self.user.email), ]) rule_b = Rule(Matcher('path', 'tests/*'), [ Owner('team', self.team.name), ]) rule_c = Rule(Matcher('path', 'src/app/*'), [ Owner('team', self.team.name), ]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b, rule_c]), fallthrough=True, auto_assignment=True, )
def setUp(self): self.user2 = self.create_user(email="*****@*****.**", is_active=True) self.create_member(user=self.user2, organization=self.organization, teams=[self.team]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema( [ grammar.Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]), grammar.Rule(Matcher("path", "*.jx"), [Owner("user", self.user2.email)]), grammar.Rule( Matcher("path", "*.cbl"), [Owner("user", self.user.email), Owner("user", self.user2.email)], ), ] ), fallthrough=True, )
def test_dump_schema(): assert dump_schema([Rule( Matcher('path', '*.js'), [Owner('team', 'frontend')] )]) == { '$version': 1, 'rules': [{ 'matcher': { 'type': 'path', 'pattern': '*.js', }, 'owners': [{ 'type': 'team', 'identifier': 'frontend', }] }] }
def setUp(self): from sentry.ownership.grammar import Rule self.user = self.create_user(email='*****@*****.**', is_active=True) self.user2 = self.create_user(email='*****@*****.**', is_active=True) self.organization = self.create_organization(owner=self.user) self.team = self.create_team(organization=self.organization) self.project = self.create_project(name='Test', teams=[self.team]) OrganizationMemberTeam.objects.create( organizationmember=OrganizationMember.objects.get( user=self.user, organization=self.organization, ), team=self.team, ) self.create_member(user=self.user2, organization=self.organization, teams=[self.team]) self.group = self.create_group( first_seen=timezone.now(), last_seen=timezone.now(), project=self.project, message='hello world', logger='root', ) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ Rule(Matcher('path', '*.py'), [ Owner('team', self.team.slug), ]), Rule(Matcher('path', '*.jx'), [ Owner('user', self.user2.email), ]), Rule(Matcher('path', '*.cbl'), [ Owner('user', self.user.email), Owner('user', self.user2.email), ]) ]), fallthrough=True, )
def test_team_without_members(self): team = self.create_team() project = self.create_project(teams=[team], fire_project_created=True) ProjectOwnership.objects.create( project_id=project.id, schema=dump_schema([Rule(Matcher("path", "*.cpp"), [Owner("team", team.slug)])]), fallthrough=True, ) rule = project.rule_set.all()[0] records = [ event_to_record(event, (rule,)) for event in self.create_events_from_filenames( project, ["hello.py", "goodbye.py", "hola.py", "adios.py"] ) ] digest = build_digest(project, sort_records(records)) user_ids = [member.user_id for member in team.member_set] assert not user_ids for user_id, user_digest in get_personalized_digests(project.id, digest, user_ids): assert False # no users in this team no digests should be processed
def make_ownership(self): rule_a = Rule( Matcher('path', 'src/*'), [ Owner('user', self.user.email), ]) rule_b = Rule( Matcher('path', 'tests/*'), [ Owner('team', self.team.name), ]) rule_c = Rule( Matcher('path', 'src/app/*'), [ Owner('team', self.team.name), ]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b, rule_c]), fallthrough=True, auto_assignment=True, )
def setUp(self): from sentry.ownership.grammar import Rule self.user = self.create_user(email="*****@*****.**", is_active=True) self.user2 = self.create_user(email="*****@*****.**", is_active=True) self.organization = self.create_organization(owner=self.user) self.team = self.create_team(organization=self.organization) self.project = self.create_project(name="Test", teams=[self.team]) OrganizationMemberTeam.objects.create( organizationmember=OrganizationMember.objects.get( user=self.user, organization=self.organization), team=self.team, ) self.create_member(user=self.user2, organization=self.organization, teams=[self.team]) self.group = self.create_group( first_seen=timezone.now(), last_seen=timezone.now(), project=self.project, message="hello world", logger="root", ) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]), Rule(Matcher("path", "*.jx"), [Owner("user", self.user2.email)]), Rule( Matcher("path", "*.cbl"), [ Owner("user", self.user.email), Owner("user", self.user2.email) ], ), ]), fallthrough=True, )
def test_team_without_members(self): team = self.create_team() project = self.create_project(teams=[team]) ProjectOwnership.objects.create( project_id=project.id, schema=dump_schema([ Rule(Matcher('path', '*.cpp'), [ Owner('team', team.slug), ]), ]), fallthrough=True, ) rule = project.rule_set.all()[0] records = [ event_to_record(event, (rule, )) for event in self.create_events(timezone.now(), project, [ 'hello.py', 'goodbye.py', 'hola.py', 'adios.py']) ] digest = build_digest(project, sort_records(records)) user_ids = [member.user_id for member in team.member_set] assert not user_ids for user_id, user_digest in get_personalized_digests(project.id, digest, user_ids): assert False # no users in this team no digests should be processed
def test_get_owners_basic(self): rule_a = Rule( Matcher('path', '*.py'), [ Owner('team', self.team.slug), ]) rule_b = Rule( Matcher('path', 'src/*'), [ Owner('user', self.user.email), ]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), fallthrough=True, ) # No data matches assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) # Match only rule_a self.assert_ownership_equals(ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'foo.py', }] } } ), ([Actor(self.team.id, Team)], [rule_a])) # Match only rule_b self.assert_ownership_equals(ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'src/thing.txt', }] } } ), ([Actor(self.user.id, User)], [rule_b])) # Matches both rule_a and rule_b self.assert_ownership_equals(ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'src/foo.py', }] } } ), ([Actor(self.user.id, User), Actor(self.team.id, Team)], [rule_a, rule_b])) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } } ) == (ProjectOwnership.Everyone, None) # When fallthrough = False, we don't implicitly assign to Everyone ProjectOwnership.objects.filter( project_id=self.project.id, ).update(fallthrough=False) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } } ) == ([], None)
def setUp(self): self.user1 = self.create_user() self.user2 = self.create_user() self.user3 = self.create_user() self.user4 = self.create_user() self.user5 = self.create_user() # this user has no events self.user_ids = [self.user1.id, self.user2.id, self.user3.id, self.user4.id, self.user5.id] self.team1 = self.create_team() self.team2 = self.create_team() self.team3 = self.create_team() self.project = self.create_project(teams=[self.team1, self.team2, self.team3]) self.create_member(user=self.user1, organization=self.organization, teams=[self.team1]) self.create_member(user=self.user2, organization=self.organization, teams=[self.team2]) self.create_member( user=self.user3, organization=self.organization, teams=[ self.team1, self.team2]) self.create_member(user=self.user4, organization=self.organization, teams=[self.team3]) self.create_member(user=self.user5, organization=self.organization, teams=[self.team3]) start_time = timezone.now() self.team1_events = self.create_events( start_time, self.project, [ 'hello.py', 'goodbye.py', 'hola.py', 'adios.py']) self.team2_events = self.create_events( start_time, self.project, [ 'old.cbl', 'retro.cbl', 'cool.cbl', 'gem.cbl']) self.user4_events = [ self.create_event( group=self.create_group( project=self.project), data=self.create_event_data( 'foo.bar', 'helloworld.org')), self.create_event( group=self.create_group( project=self.project), data=self.create_event_data( 'bar.foo', 'helloworld.org')), ] self.team1_matcher = Matcher('path', '*.py') self.team2_matcher = Matcher('path', '*.cbl') self.user4_matcher = Matcher('url', '*.org') ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ Rule(self.team1_matcher, [ Owner('team', self.team1.slug), Owner('user', self.user3.email), ]), Rule(self.team2_matcher, [ Owner('team', self.team2.slug), ]), Rule(self.user4_matcher, [ Owner('user', self.user4.email), ]), ]), fallthrough=True, )