123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921 |
- from datetime import UTC, datetime, timedelta
- import pytest
- from django.utils import timezone
- from sentry.models.group import GroupStatus
- from sentry.models.release import Release
- from sentry.models.releaseprojectenvironment import ReleaseProjectEnvironment
- from sentry.models.releases.release_project import ReleaseProject
- from sentry.models.team import Team
- from sentry.search.base import ANY
- from sentry.search.utils import (
- DEVICE_CLASS,
- InvalidQuery,
- LatestReleaseOrders,
- convert_user_tag_to_query,
- get_first_last_release_for_group,
- get_latest_release,
- get_numeric_field_value,
- parse_duration,
- parse_numeric_value,
- parse_query,
- parse_size,
- tokenize_query,
- )
- from sentry.testutils.cases import APITestCase, SnubaTestCase, TestCase
- from sentry.testutils.helpers.datetime import before_now, freeze_time, iso_format
- from sentry.testutils.silo import control_silo_test
- from sentry.users.services.user.model import RpcUser
- from sentry.users.services.user.serial import serialize_rpc_user
- from sentry.users.services.user.service import user_service
- def test_get_numeric_field_value():
- assert get_numeric_field_value("foo", "10") == {"foo": 10}
- assert get_numeric_field_value("foo", ">10") == {"foo_lower": 10, "foo_lower_inclusive": False}
- assert get_numeric_field_value("foo", ">=10") == {"foo_lower": 10, "foo_lower_inclusive": True}
- assert get_numeric_field_value("foo", "<10") == {"foo_upper": 10, "foo_upper_inclusive": False}
- assert get_numeric_field_value("foo", "<=10") == {"foo_upper": 10, "foo_upper_inclusive": True}
- assert get_numeric_field_value("foo", ">3.5", type=float) == {
- "foo_lower": 3.5,
- "foo_lower_inclusive": False,
- }
- assert get_numeric_field_value("foo", "<=-3.5", type=float) == {
- "foo_upper": -3.5,
- "foo_upper_inclusive": True,
- }
- class TestParseNumericValue(TestCase):
- def test_simple(self):
- assert parse_numeric_value("10", None) == 10
- def test_k(self):
- assert parse_numeric_value("1", "k") == 1000.0
- assert parse_numeric_value("1", "K") == 1000.0
- def test_m(self):
- assert parse_numeric_value("1", "m") == 1000000.0
- assert parse_numeric_value("1", "M") == 1000000.0
- def test_b(self):
- assert parse_numeric_value("1", "b") == 1000000000.0
- assert parse_numeric_value("1", "B") == 1000000000.0
- class TestParseSizeValue(TestCase):
- def test_simple(self):
- assert parse_size("8", "bit") == 1
- def test_uppercase(self):
- assert parse_size("1", "KB") == 1000
- assert parse_size("1", "kb") == 1000
- assert parse_size("1", "Kb") == 1000
- class TestParseDuration(TestCase):
- def test_ms(self):
- assert parse_duration("123", "ms") == 123
- def test_sec(self):
- assert parse_duration("456", "s") == 456000
- def test_minutes(self):
- assert parse_duration("789", "min") == 789 * 60 * 1000
- assert parse_duration("789", "m") == 789 * 60 * 1000
- def test_hours(self):
- assert parse_duration("234", "hr") == 234 * 60 * 60 * 1000
- assert parse_duration("234", "h") == 234 * 60 * 60 * 1000
- def test_days(self):
- assert parse_duration("567", "day") == 567 * 24 * 60 * 60 * 1000
- assert parse_duration("567", "d") == 567 * 24 * 60 * 60 * 1000
- def test_weeks(self):
- assert parse_duration("890", "wk") == 890 * 7 * 24 * 60 * 60 * 1000
- assert parse_duration("890", "w") == 890 * 7 * 24 * 60 * 60 * 1000
- def test_errors(self):
- with pytest.raises(InvalidQuery):
- parse_duration("test", "ms")
- with pytest.raises(InvalidQuery):
- parse_duration("123", "test")
- def test_large_durations(self):
- max_duration = 999999999 * 24 * 60 * 60 * 1000
- assert parse_duration("999999999", "d") == max_duration
- assert parse_duration(str(999999999 * 24), "h") == max_duration
- assert parse_duration(str(999999999 * 24 * 60), "m") == max_duration
- assert parse_duration(str(999999999 * 24 * 60 * 60), "s") == max_duration
- assert parse_duration(str(999999999 * 24 * 60 * 60 * 1000), "ms") == max_duration
- def test_overflow_durations(self):
- with pytest.raises(InvalidQuery):
- assert parse_duration(str(999999999 + 1), "d")
- with pytest.raises(InvalidQuery):
- assert parse_duration(str((999999999 + 1) * 24), "h")
- with pytest.raises(InvalidQuery):
- assert parse_duration(str((999999999 + 1) * 24 * 60 + 1), "m")
- with pytest.raises(InvalidQuery):
- assert parse_duration(str((999999999 + 1) * 24 * 60 * 60 + 1), "s")
- with pytest.raises(InvalidQuery):
- assert parse_duration(str((999999999 + 1) * 24 * 60 * 60 * 1000 + 1), "ms")
- def test_tokenize_query_only_keyed_fields():
- tests = [
- ("a:a", {"a": ["a"]}),
- ("(a:a AND b:b)", {"a": ["a"], "b": ["b"]}),
- ("( a:a AND (b:b OR c:c))", {"a": ["a"], "b": ["b"], "c": ["c"]}),
- ("( a:a AND (b:b OR c:c ) )", {"a": ["a"], "b": ["b"], "c": ["c"]}),
- (
- "(x y a:a AND (b:b OR c:c) z)",
- {"a": ["a"], "b": ["b"], "c": ["c"], "query": ["x", "y", "z"]},
- ),
- (
- "((x y)) a:a AND (b:b OR c:c) z)",
- {"a": ["a"], "b": ["b"], "c": ["c"], "query": ["x", "y", "z"]},
- ),
- (
- "((x y)) a():>a AND (!b:b OR c():<c) z)",
- {"a()": [">a"], "!b": ["b"], "c()": ["<c"], "query": ["x", "y", "z"]},
- ),
- ('a:"\\"a\\""', {"a": ['\\"a\\"']}),
- (
- 'a:"i \\" quote" b:"b\\"bb" c:"cc"',
- {"a": ['i \\" quote'], "b": ['b\\"bb'], "c": ["cc"]},
- ),
- ]
- for test in tests:
- assert tokenize_query(test[0]) == test[1], test[0]
- def test_get_numeric_field_value_invalid():
- with pytest.raises(InvalidQuery):
- get_numeric_field_value("foo", ">=1k")
- class ParseQueryTest(APITestCase, SnubaTestCase):
- @property
- def rpc_user(self):
- return user_service.get_user(user_id=self.user.id)
- @property
- def current_rpc_user(self):
- # This doesn't include useremails. Used in filters
- # where the current user is passed back
- return serialize_rpc_user(self.user)
- def parse_query(self, query):
- return parse_query([self.project], query, self.user, [])
- def test_simple(self):
- result = self.parse_query("foo bar")
- assert result == {"tags": {}, "query": "foo bar"}
- def test_useless_prefix(self):
- result = self.parse_query("foo: bar")
- assert result == {"tags": {}, "query": "foo: bar"}
- def test_useless_prefix_with_symbol(self):
- result = self.parse_query("foo: @ba$r")
- assert result == {"tags": {}, "query": "foo: @ba$r"}
- def test_useless_prefix_with_colon(self):
- result = self.parse_query("foo: :ba:r::foo:")
- assert result == {"tags": {}, "query": "foo: :ba:r::foo:"}
- def test_handles_space_separation_after_useless_prefix_exception(self):
- result = self.parse_query("foo: bar foo:bar")
- assert result == {"tags": {"foo": "bar"}, "query": "foo: bar"}
- def test_handles_period_in_tag_key(self):
- result = self.parse_query("foo.bar:foobar")
- assert result == {"tags": {"foo.bar": "foobar"}, "query": ""}
- def test_handles_dash_in_tag_key(self):
- result = self.parse_query("foo-bar:foobar")
- assert result == {"tags": {"foo-bar": "foobar"}, "query": ""}
- # TODO: update docs to include minutes, days, and weeks suffixes
- @freeze_time("2016-01-01")
- def test_age_tag_negative_value(self):
- start = timezone.now()
- expected = start - timedelta(hours=12)
- result = self.parse_query("age:-12h")
- assert result == {"tags": {}, "query": "", "age_from": expected, "age_from_inclusive": True}
- @freeze_time("2016-01-01")
- def test_age_tag_positive_value(self):
- start = timezone.now()
- expected = start - timedelta(hours=12)
- result = self.parse_query("age:+12h")
- assert result == {"tags": {}, "query": "", "age_to": expected, "age_to_inclusive": True}
- @freeze_time("2016-01-01")
- def test_age_tag_weeks(self):
- start = timezone.now()
- expected = start - timedelta(days=35)
- result = self.parse_query("age:+5w")
- assert result == {"tags": {}, "query": "", "age_to": expected, "age_to_inclusive": True}
- @freeze_time("2016-01-01")
- def test_age_tag_days(self):
- start = timezone.now()
- expected = start - timedelta(days=10)
- result = self.parse_query("age:+10d")
- assert result == {"tags": {}, "query": "", "age_to": expected, "age_to_inclusive": True}
- @freeze_time("2016-01-01")
- def test_age_tag_hours(self):
- start = timezone.now()
- expected = start - timedelta(hours=10)
- result = self.parse_query("age:+10h")
- assert result == {"tags": {}, "query": "", "age_to": expected, "age_to_inclusive": True}
- @freeze_time("2016-01-01")
- def test_age_tag_minutes(self):
- start = timezone.now()
- expected = start - timedelta(minutes=30)
- result = self.parse_query("age:+30m")
- assert result == {"tags": {}, "query": "", "age_to": expected, "age_to_inclusive": True}
- @freeze_time("2016-01-01")
- def test_two_age_tags(self):
- start = timezone.now()
- expected_to = start - timedelta(hours=12)
- expected_from = start - timedelta(hours=24)
- result = self.parse_query("age:+12h age:-24h")
- assert result == {
- "tags": {},
- "query": "",
- "age_to": expected_to,
- "age_from": expected_from,
- "age_to_inclusive": True,
- "age_from_inclusive": True,
- }
- def test_event_timestamp_syntax(self):
- result = self.parse_query("event.timestamp:2016-01-02")
- assert result == {
- "query": "",
- "date_from": datetime(2016, 1, 2, tzinfo=UTC),
- "date_from_inclusive": True,
- "date_to": datetime(2016, 1, 3, tzinfo=UTC),
- "date_to_inclusive": False,
- "tags": {},
- }
- def test_times_seen_syntax(self):
- result = self.parse_query("timesSeen:10")
- assert result == {"tags": {}, "times_seen": 10, "query": ""}
- # TODO: query parser for '>' timestamp should set inclusive to False.
- @pytest.mark.xfail
- def test_greater_than_comparator(self):
- result = self.parse_query("timesSeen:>10 event.timestamp:>2016-01-02")
- assert result == {
- "tags": {},
- "query": "",
- "times_seen_lower": 10,
- "times_seen_lower_inclusive": False,
- "date_from": datetime(2016, 1, 2, tzinfo=UTC),
- "date_from_inclusive": False,
- }
- def test_greater_than_equal_comparator(self):
- result = self.parse_query("timesSeen:>=10 event.timestamp:>=2016-01-02")
- assert result == {
- "tags": {},
- "query": "",
- "times_seen_lower": 10,
- "times_seen_lower_inclusive": True,
- "date_from": datetime(2016, 1, 2, tzinfo=UTC),
- "date_from_inclusive": True,
- }
- def test_less_than_comparator(self):
- result = self.parse_query("event.timestamp:<2016-01-02 timesSeen:<10")
- assert result == {
- "tags": {},
- "query": "",
- "times_seen_upper": 10,
- "times_seen_upper_inclusive": False,
- "date_to": datetime(2016, 1, 2, tzinfo=UTC),
- "date_to_inclusive": False,
- }
- # TODO: query parser for '<=' timestamp should set inclusive to True.
- @pytest.mark.xfail
- def test_less_than_equal_comparator(self):
- result = self.parse_query("event.timestamp:<=2016-01-02 timesSeen:<=10")
- assert result == {
- "tags": {},
- "query": "",
- "times_seen_upper": 10,
- "times_seen_upper_inclusive": True,
- "date_to": datetime(2016, 1, 2, tzinfo=UTC),
- "date_to_inclusive": True,
- }
- def test_handles_underscore_in_tag_key(self):
- result = self.parse_query("foo_bar:foobar")
- assert result == {"tags": {"foo_bar": "foobar"}, "query": ""}
- def test_mix_tag_and_query(self):
- result = self.parse_query("foo bar key:value")
- assert result == {"tags": {"key": "value"}, "query": "foo bar"}
- def test_single_tag(self):
- result = self.parse_query("key:value")
- assert result == {"tags": {"key": "value"}, "query": ""}
- def test_tag_with_colon_in_value(self):
- result = self.parse_query("url:http://example.com")
- assert result == {"tags": {"url": "http://example.com"}, "query": ""}
- def test_single_space_in_value(self):
- result = self.parse_query('key:"value1 value2"')
- assert result == {"tags": {"key": "value1 value2"}, "query": ""}
- def test_multiple_spaces_in_value(self):
- result = self.parse_query('key:"value1 value2"')
- assert result == {"tags": {"key": "value1 value2"}, "query": ""}
- def test_invalid_tag_as_query(self):
- result = self.parse_query("Resque::DirtyExit")
- assert result == {"tags": {}, "query": "Resque::DirtyExit"}
- def test_colons_in_tag_value(self):
- result = self.parse_query("key:Resque::DirtyExit")
- assert result == {"tags": {"key": "Resque::DirtyExit"}, "query": ""}
- def test_multiple_tags(self):
- result = self.parse_query("foo:bar key:value")
- assert result == {"tags": {"key": "value", "foo": "bar"}, "query": ""}
- def test_single_tag_with_quotes(self):
- result = self.parse_query('foo:"bar"')
- assert result == {"tags": {"foo": "bar"}, "query": ""}
- def test_tag_with_quotes_and_query(self):
- result = self.parse_query('key:"a value" hello')
- assert result == {"tags": {"key": "a value"}, "query": "hello"}
- def test_is_resolved(self):
- result = self.parse_query("is:resolved")
- assert result == {"status": GroupStatus.RESOLVED, "tags": {}, "query": ""}
- def test_assigned_me(self):
- result = self.parse_query("assigned:me")
- assert result == {"assigned_to": self.current_rpc_user, "tags": {}, "query": ""}
- def test_assigned_none(self):
- result = self.parse_query("assigned:none")
- assert result == {"assigned_to": None, "tags": {}, "query": ""}
- def test_assigned_email(self):
- result = self.parse_query(f"assigned:{self.user.email}")
- assert result == {"assigned_to": self.rpc_user, "tags": {}, "query": ""}
- def test_assigned_unknown_user(self):
- result = self.parse_query("assigned:fake@example.com")
- assert isinstance(result["assigned_to"], RpcUser)
- assert result["assigned_to"].id == 0
- def test_assigned_valid_team(self):
- result = self.parse_query(f"assigned:#{self.team.slug}")
- assert result["assigned_to"] == self.team
- def test_assigned_unassociated_team(self):
- team2 = self.create_team(organization=self.organization)
- result = self.parse_query(f"assigned:#{team2.slug}")
- assert isinstance(result["assigned_to"], Team)
- assert result["assigned_to"].id == 0
- def test_assigned_invalid_team(self):
- result = self.parse_query("assigned:#invalid")
- assert isinstance(result["assigned_to"], Team)
- assert result["assigned_to"].id == 0
- def test_bookmarks_me(self):
- result = self.parse_query("bookmarks:me")
- assert result == {"bookmarked_by": self.current_rpc_user, "tags": {}, "query": ""}
- def test_bookmarks_email(self):
- result = self.parse_query(f"bookmarks:{self.user.email}")
- assert result == {"bookmarked_by": self.rpc_user, "tags": {}, "query": ""}
- def test_bookmarks_unknown_user(self):
- result = self.parse_query("bookmarks:fake@example.com")
- assert result["bookmarked_by"].id == 0
- def test_first_release(self):
- result = self.parse_query("first-release:bar")
- assert result == {"first_release": ["bar"], "tags": {}, "query": ""}
- def test_first_release_latest(self):
- result = self.parse_query("first-release:latest")
- assert result == {"first_release": [""], "tags": {}, "query": ""}
- release = self.create_release(
- project=self.project,
- version="older_release",
- date_added=timezone.now() - timedelta(days=1),
- )
- result = self.parse_query("first-release:latest")
- assert result == {"first_release": [release.version], "tags": {}, "query": ""}
- release = self.create_release(
- project=self.project, version="new_release", date_added=timezone.now()
- )
- result = self.parse_query("first-release:latest")
- assert result == {"first_release": [release.version], "tags": {}, "query": ""}
- def test_release(self):
- result = self.parse_query("release:bar")
- assert result == {"tags": {"sentry:release": ["bar"]}, "query": ""}
- def test_release_latest(self):
- result = self.parse_query("release:latest")
- assert result == {"tags": {"sentry:release": [""]}, "query": ""}
- release = self.create_release(
- project=self.project,
- version="older_release",
- date_added=timezone.now() - timedelta(days=1),
- )
- result = self.parse_query("release:latest")
- assert result == {"tags": {"sentry:release": [release.version]}, "query": ""}
- release = self.create_release(
- project=self.project, version="new_release", date_added=timezone.now()
- )
- result = self.parse_query("release:latest")
- assert result == {"tags": {"sentry:release": [release.version]}, "query": ""}
- def test_dist(self):
- result = self.parse_query("dist:123")
- assert result == {"tags": {"sentry:dist": "123"}, "query": ""}
- def test_padded_spacing(self):
- result = self.parse_query("release:bar foo bar")
- assert result == {"tags": {"sentry:release": ["bar"]}, "query": "foo bar"}
- def test_unknown_user_with_dot_query(self):
- result = self.parse_query("user.email:fake@example.com")
- assert result["tags"]["sentry:user"] == "email:fake@example.com"
- def test_unknown_user_value(self):
- result = self.parse_query("user.xxxxxx:example")
- assert result["tags"]["sentry:user"] == "xxxxxx:example"
- def test_user_lookup_with_dot_query(self):
- self.project.date_added = timezone.now() - timedelta(minutes=10)
- self.project.save()
- self.store_event(
- data={
- "user": {
- "id": 1,
- "email": "foo@example.com",
- "username": "foobar",
- "ip_address": "127.0.0.1",
- },
- "timestamp": iso_format(before_now(seconds=10)),
- },
- project_id=self.project.id,
- )
- result = self.parse_query("user.username:foobar")
- assert result["tags"]["sentry:user"] == "id:1"
- def test_unknown_user_legacy_syntax(self):
- result = self.parse_query("user:email:fake@example.com")
- assert result["tags"]["sentry:user"] == "email:fake@example.com"
- def test_user_lookup_legacy_syntax(self):
- self.project.date_added = timezone.now() - timedelta(minutes=10)
- self.project.save()
- self.store_event(
- data={
- "user": {
- "id": 1,
- "email": "foo@example.com",
- "username": "foobar",
- "ip_address": "127.0.0.1",
- },
- "timestamp": iso_format(before_now(seconds=10)),
- },
- project_id=self.project.id,
- )
- result = self.parse_query("user:username:foobar")
- assert result["tags"]["sentry:user"] == "id:1"
- def test_is_unassigned(self):
- result = self.parse_query("is:unassigned")
- assert result == {"unassigned": True, "tags": {}, "query": ""}
- def test_is_assigned(self):
- result = self.parse_query("is:assigned")
- assert result == {"unassigned": False, "tags": {}, "query": ""}
- def test_is_inbox(self):
- result = self.parse_query("is:for_review")
- assert result == {"for_review": True, "tags": {}, "query": ""}
- def test_is_unlinked(self):
- result = self.parse_query("is:unlinked")
- assert result == {"linked": False, "tags": {}, "query": ""}
- def test_is_linked(self):
- result = self.parse_query("is:linked")
- assert result == {"linked": True, "tags": {}, "query": ""}
- def test_age_from(self):
- result = self.parse_query("age:-24h")
- assert result["age_from"] > timezone.now() - timedelta(hours=25)
- assert result["age_from"] < timezone.now() - timedelta(hours=23)
- assert not result.get("age_to")
- def test_age_to(self):
- result = self.parse_query("age:+24h")
- assert result["age_to"] > timezone.now() - timedelta(hours=25)
- assert result["age_to"] < timezone.now() - timedelta(hours=23)
- assert not result.get("age_from")
- def test_age_range(self):
- result = self.parse_query("age:-24h age:+12h")
- assert result["age_from"] > timezone.now() - timedelta(hours=25)
- assert result["age_from"] < timezone.now() - timedelta(hours=23)
- assert result["age_to"] > timezone.now() - timedelta(hours=13)
- assert result["age_to"] < timezone.now() - timedelta(hours=11)
- def test_first_seen_range(self):
- result = self.parse_query("firstSeen:-24h firstSeen:+12h")
- assert result["age_from"] > timezone.now() - timedelta(hours=25)
- assert result["age_from"] < timezone.now() - timedelta(hours=23)
- assert result["age_to"] > timezone.now() - timedelta(hours=13)
- assert result["age_to"] < timezone.now() - timedelta(hours=11)
- def test_date_range(self):
- result = self.parse_query("event.timestamp:>2016-01-01 event.timestamp:<2016-01-02")
- assert result["date_from"] == datetime(2016, 1, 1, tzinfo=UTC)
- assert result["date_from_inclusive"] is False
- assert result["date_to"] == datetime(2016, 1, 2, tzinfo=UTC)
- assert result["date_to_inclusive"] is False
- def test_date_range_with_timezone(self):
- result = self.parse_query(
- "event.timestamp:>2016-01-01T10:00:00-03:00 event.timestamp:<2016-01-02T10:00:00+02:00"
- )
- assert result["date_from"] == datetime(2016, 1, 1, 13, 0, 0, tzinfo=UTC)
- assert result["date_from_inclusive"] is False
- assert result["date_to"] == datetime(2016, 1, 2, 8, 0, tzinfo=UTC)
- assert result["date_to_inclusive"] is False
- def test_date_range_with_z_timezone(self):
- result = self.parse_query(
- "event.timestamp:>2016-01-01T10:00:00Z event.timestamp:<2016-01-02T10:00:00Z"
- )
- assert result["date_from"] == datetime(2016, 1, 1, 10, 0, 0, tzinfo=UTC)
- assert result["date_from_inclusive"] is False
- assert result["date_to"] == datetime(2016, 1, 2, 10, 0, tzinfo=UTC)
- assert result["date_to_inclusive"] is False
- def test_date_range_inclusive(self):
- result = self.parse_query("event.timestamp:>=2016-01-01 event.timestamp:<=2016-01-02")
- assert result["date_from"] == datetime(2016, 1, 1, tzinfo=UTC)
- assert result["date_from_inclusive"] is True
- assert result["date_to"] == datetime(2016, 1, 2, tzinfo=UTC)
- assert result["date_to_inclusive"] is True
- def test_date_approx_day(self):
- date_value = datetime(2016, 1, 1, tzinfo=UTC)
- result = self.parse_query("event.timestamp:2016-01-01")
- assert result["date_from"] == date_value
- assert result["date_from_inclusive"]
- assert result["date_to"] == date_value + timedelta(days=1)
- assert not result["date_to_inclusive"]
- def test_date_approx_precise(self):
- date_value = datetime(2016, 1, 1, tzinfo=UTC)
- result = self.parse_query("event.timestamp:2016-01-01T00:00:00")
- assert result["date_from"] == date_value - timedelta(minutes=5)
- assert result["date_from_inclusive"]
- assert result["date_to"] == date_value + timedelta(minutes=6)
- assert not result["date_to_inclusive"]
- def test_date_approx_precise_with_timezone(self):
- date_value = datetime(2016, 1, 1, 5, 0, 0, tzinfo=UTC)
- result = self.parse_query("event.timestamp:2016-01-01T00:00:00-05:00")
- assert result["date_from"] == date_value - timedelta(minutes=5)
- assert result["date_from_inclusive"]
- assert result["date_to"] == date_value + timedelta(minutes=6)
- assert not result["date_to_inclusive"]
- def test_last_seen_range(self):
- result = self.parse_query("lastSeen:-24h lastSeen:+12h")
- assert result["last_seen_from"] > timezone.now() - timedelta(hours=25)
- assert result["last_seen_from"] < timezone.now() - timedelta(hours=23)
- assert result["last_seen_to"] > timezone.now() - timedelta(hours=13)
- assert result["last_seen_to"] < timezone.now() - timedelta(hours=11)
- def test_has_tag(self):
- result = self.parse_query("has:foo")
- assert result["tags"]["foo"] == ANY
- result = self.parse_query("has:foo foo:value")
- assert result["tags"]["foo"] == "value"
- def test_has_user(self):
- result = self.parse_query("has:user")
- assert result["tags"]["sentry:user"] == ANY
- def test_has_release(self):
- result = self.parse_query("has:release")
- assert result["tags"]["sentry:release"] == ANY
- def test_quoted_string(self):
- result = self.parse_query('"release:foo"')
- assert result == {"tags": {}, "query": "release:foo"}
- def test_quoted_tag_value(self):
- result = self.parse_query('event.type:error title:"QueryExecutionError: Code: 141."')
- assert result["query"] == ""
- assert result["tags"]["title"] == "QueryExecutionError: Code: 141."
- assert result["tags"]["event.type"] == "error"
- def test_leading_colon(self):
- result = self.parse_query("country:canada :unresolved")
- assert result["query"] == ":unresolved"
- assert result["tags"]["country"] == "canada"
- def test_assigned_or_suggested_me(self):
- result = self.parse_query("assigned_or_suggested:me")
- assert result == {"assigned_or_suggested": self.current_rpc_user, "tags": {}, "query": ""}
- def test_assigned_or_suggested_none(self):
- result = self.parse_query("assigned_or_suggested:none")
- assert result == {
- "assigned_or_suggested": None,
- "tags": {},
- "query": "",
- }
- def test_owner_email(self):
- result = self.parse_query(f"assigned_or_suggested:{self.user.email}")
- assert result == {"assigned_or_suggested": self.rpc_user, "tags": {}, "query": ""}
- def test_assigned_or_suggested_unknown_user(self):
- result = self.parse_query("assigned_or_suggested:fake@example.com")
- assert isinstance(result["assigned_or_suggested"], RpcUser)
- assert result["assigned_or_suggested"].id == 0
- def test_owner_valid_team(self):
- result = self.parse_query(f"assigned_or_suggested:#{self.team.slug}")
- assert result["assigned_or_suggested"] == self.team
- def test_assigned_or_suggested_unassociated_team(self):
- team2 = self.create_team(organization=self.organization)
- result = self.parse_query(f"assigned_or_suggested:#{team2.slug}")
- assert isinstance(result["assigned_or_suggested"], Team)
- assert result["assigned_or_suggested"].id == 0
- def test_owner_invalid_team(self):
- result = self.parse_query("assigned_or_suggested:#invalid")
- assert isinstance(result["assigned_or_suggested"], Team)
- assert result["assigned_or_suggested"].id == 0
- class GetLatestReleaseTest(TestCase):
- def test(self):
- with pytest.raises(Release.DoesNotExist):
- # no releases exist period
- environment = None
- get_latest_release([self.project], environment)
- old = self.create_release(version="old")
- new_date = old.date_added + timedelta(minutes=1)
- new = self.create_release(
- version="new-but-in-environment",
- environments=[self.environment],
- date_released=new_date,
- )
- newest = self.create_release(
- version="newest-overall", date_released=old.date_added + timedelta(minutes=5)
- )
- # latest overall (no environment filter)
- environment = None
- result = get_latest_release([self.project], environment)
- assert result == [newest.version]
- # latest in environment
- environment = self.environment
- result = get_latest_release([self.project], [environment])
- assert result == [new.version]
- assert get_latest_release([self.project.id], [environment]) == []
- assert get_latest_release(
- [self.project.id], [environment], self.project.organization_id
- ) == [new.version]
- # Verify that not passing an environment correctly gets the latest one
- assert get_latest_release([self.project], None) == [newest.version]
- assert get_latest_release([self.project], []) == [newest.version]
- with pytest.raises(Release.DoesNotExist):
- # environment with no releases
- new_environment = self.create_environment()
- get_latest_release([self.project], [new_environment])
- project_2 = self.create_project()
- other_project_env_release = self.create_release(
- project_2, version="other_project_env", environments=[self.environment]
- )
- other_project_release = self.create_release(project_2, version="other_project")
- assert get_latest_release([project_2], None) == [other_project_release.version]
- assert get_latest_release([project_2], [environment]) == [other_project_env_release.version]
- assert get_latest_release([self.project, project_2], None) == [
- newest.version,
- other_project_release.version,
- ]
- assert get_latest_release([self.project, project_2], [environment]) == [
- new.version,
- other_project_env_release.version,
- ]
- with pytest.raises(Release.DoesNotExist):
- assert get_latest_release([self.project, project_2], [environment], adopted=True) == [
- new.version,
- other_project_env_release.version,
- ]
- ReleaseProjectEnvironment.objects.filter(
- release__in=[new, other_project_env_release]
- ).update(adopted=timezone.now())
- assert get_latest_release([self.project, project_2], [environment], adopted=True) == [
- new.version,
- other_project_env_release.version,
- ]
- def test_semver(self):
- project_2 = self.create_project()
- release_1 = self.create_release(version="test@2.0.0", environments=[self.environment])
- env_2 = self.create_environment()
- self.create_release(version="test@1.3.2", environments=[env_2])
- self.create_release(version="test@1.0.0", environments=[self.environment, env_2])
- # Check when we're using a single project that we sort by semver
- assert get_latest_release([self.project], None) == [release_1.version]
- assert get_latest_release([project_2, self.project], None) == [release_1.version]
- release_3 = self.create_release(
- project_2, version="test@1.3.3", environments=[self.environment, env_2]
- )
- assert get_latest_release([project_2, self.project], None) == [
- release_3.version,
- release_1.version,
- ]
- with pytest.raises(Release.DoesNotExist):
- get_latest_release([project_2, self.project], [self.environment, env_2], adopted=True)
- ReleaseProjectEnvironment.objects.filter(release__in=[release_3, release_1]).update(
- adopted=timezone.now()
- )
- assert get_latest_release(
- [project_2, self.project], [self.environment, env_2], adopted=True
- ) == [
- release_3.version,
- release_1.version,
- ]
- assert get_latest_release([project_2, self.project], [env_2], adopted=True) == [
- release_3.version,
- ]
- # Make sure unadopted releases are ignored
- ReleaseProjectEnvironment.objects.filter(release__in=[release_3]).update(
- unadopted=timezone.now()
- )
- assert get_latest_release(
- [project_2, self.project], [self.environment, env_2], adopted=True
- ) == [
- release_1.version,
- ]
- ReleaseProject.objects.filter(release__in=[release_1]).update(adopted=timezone.now())
- assert get_latest_release([project_2, self.project], None, adopted=True) == [
- release_1.version,
- ]
- def test_multiple_projects_mixed_versions(self):
- project_2 = self.create_project()
- release_1 = self.create_release(version="test@2.0.0")
- self.create_release(project_2, version="not_semver")
- release_2 = self.create_release(project_2, version="not_semver_2")
- self.create_release(version="test@1.0.0")
- assert get_latest_release([project_2, self.project], None) == [
- release_2.version,
- release_1.version,
- ]
- class GetFirstLastReleaseForGroupTest(TestCase):
- def test_date(self):
- with pytest.raises(Release.DoesNotExist):
- get_first_last_release_for_group(self.group, LatestReleaseOrders.DATE, True)
- oldest = self.create_release(version="old")
- self.create_group_release(group=self.group, release=oldest)
- newest = self.create_release(
- version="newest", date_released=oldest.date_added + timedelta(minutes=5)
- )
- self.create_group_release(group=self.group, release=newest)
- assert newest == get_first_last_release_for_group(
- self.group, LatestReleaseOrders.DATE, True
- )
- assert oldest == get_first_last_release_for_group(
- self.group, LatestReleaseOrders.DATE, False
- )
- group_2 = self.create_group()
- with pytest.raises(Release.DoesNotExist):
- get_first_last_release_for_group(group_2, LatestReleaseOrders.DATE, True)
- self.create_group_release(group=group_2, release=oldest)
- assert oldest == get_first_last_release_for_group(group_2, LatestReleaseOrders.DATE, True)
- assert oldest == get_first_last_release_for_group(group_2, LatestReleaseOrders.DATE, False)
- def test_semver(self):
- with pytest.raises(Release.DoesNotExist):
- get_first_last_release_for_group(self.group, LatestReleaseOrders.SEMVER, True)
- latest = self.create_release(version="test@2.0.0")
- middle = self.create_release(version="test@1.3.2")
- earliest = self.create_release(
- version="test@1.0.0", date_released=latest.date_added + timedelta(minutes=5)
- )
- self.create_group_release(group=self.group, release=latest)
- self.create_group_release(group=self.group, release=middle)
- self.create_group_release(group=self.group, release=earliest)
- assert latest == get_first_last_release_for_group(
- self.group, LatestReleaseOrders.SEMVER, True
- )
- assert earliest == get_first_last_release_for_group(
- self.group, LatestReleaseOrders.DATE, True
- )
- assert earliest == get_first_last_release_for_group(
- self.group, LatestReleaseOrders.SEMVER, False
- )
- assert latest == get_first_last_release_for_group(
- self.group, LatestReleaseOrders.DATE, False
- )
- group_2 = self.create_group()
- with pytest.raises(Release.DoesNotExist):
- get_first_last_release_for_group(group_2, LatestReleaseOrders.SEMVER, True)
- self.create_group_release(group=group_2, release=latest)
- self.create_group_release(group=group_2, release=middle)
- assert latest == get_first_last_release_for_group(group_2, LatestReleaseOrders.SEMVER, True)
- assert middle == get_first_last_release_for_group(
- group_2, LatestReleaseOrders.SEMVER, False
- )
- @control_silo_test
- class ConvertUserTagTest(TestCase):
- def test_simple_user_tag(self):
- assert convert_user_tag_to_query("user", "id:123456") == 'user.id:"123456"'
- def test_user_tag_with_quote(self):
- assert convert_user_tag_to_query("user", 'id:123"456') == 'user.id:"123\\"456"'
- def test_user_tag_with_space(self):
- assert convert_user_tag_to_query("user", "id:123 456") == 'user.id:"123 456"'
- def test_non_user_tag(self):
- assert convert_user_tag_to_query("user", 'fake:123"456') is None
- def test_valid_device_class_mapping():
- assert set(DEVICE_CLASS.keys()) == {"low", "medium", "high"}, "Only 3 possible classes"
- # should all be integers
- device_classes = {key: {int(value) for value in values} for key, values in DEVICE_CLASS.items()}
- assert all(
- 0 not in values for values in device_classes.values()
- ), "`0` is not a valid classes as it represents unclassified"
|