mirror of https://github.com/zulip/zulip.git
187 lines
8.7 KiB
Python
187 lines
8.7 KiB
Python
from django.db import models
|
|
from django.test import TestCase
|
|
from django.utils import timezone
|
|
|
|
from analytics.lib.counts import CountStat, COUNT_STATS, process_count_stat, \
|
|
zerver_count_user_by_realm, zerver_count_message_by_user, \
|
|
zerver_count_message_by_stream, zerver_count_stream_by_realm, \
|
|
do_fill_count_stat_at_hour, ZerverCountQuery
|
|
from analytics.models import BaseCount, InstallationCount, RealmCount, \
|
|
UserCount, StreamCount, FillState, installation_epoch
|
|
from zerver.models import Realm, UserProfile, Message, Stream, Recipient, \
|
|
Huddle, get_user_profile_by_email, get_client
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from typing import Any, Type, Optional, Text, Tuple
|
|
|
|
class AnalyticsTestCase(TestCase):
|
|
MINUTE = timedelta(seconds = 60)
|
|
HOUR = MINUTE * 60
|
|
DAY = HOUR * 24
|
|
TIME_ZERO = datetime(1988, 3, 14).replace(tzinfo=timezone.utc)
|
|
TIME_LAST_HOUR = TIME_ZERO - HOUR
|
|
|
|
def setUp(self):
|
|
# type: () -> None
|
|
self.default_realm = Realm.objects.create(
|
|
string_id='realmtest', name='Realm Test',
|
|
domain='test.analytics', date_created=self.TIME_ZERO - 2*self.DAY)
|
|
self.name_counter = 100
|
|
|
|
# Lightweight creation of users, streams, and messages
|
|
def create_user(self, **kwargs):
|
|
# type: (**Any) -> UserProfile
|
|
self.name_counter += 1
|
|
defaults = {
|
|
'email': 'user%s@domain.tld' % (self.name_counter,),
|
|
'date_joined': self.TIME_LAST_HOUR,
|
|
'full_name': 'full_name',
|
|
'short_name': 'short_name',
|
|
'pointer': -1,
|
|
'last_pointer_updater': 'seems unused?',
|
|
'realm': self.default_realm,
|
|
'api_key': '42'}
|
|
for key, value in defaults.items():
|
|
kwargs[key] = kwargs.get(key, value)
|
|
return UserProfile.objects.create(**kwargs)
|
|
|
|
def create_stream_with_recipient(self, **kwargs):
|
|
# type: (**Any) -> Tuple[Stream, Recipient]
|
|
self.name_counter += 1
|
|
defaults = {'name': 'stream name %s' % (self.name_counter,),
|
|
'realm': self.default_realm,
|
|
'date_created': self.TIME_LAST_HOUR}
|
|
for key, value in defaults.items():
|
|
kwargs[key] = kwargs.get(key, value)
|
|
stream = Stream.objects.create(**kwargs)
|
|
recipient = Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM)
|
|
return stream, recipient
|
|
|
|
def create_huddle_with_recipient(self, **kwargs):
|
|
# type: (**Any) -> Tuple[Huddle, Recipient]
|
|
self.name_counter += 1
|
|
defaults = {'huddle_hash': 'hash%s' % (self.name_counter,)}
|
|
for key, value in defaults.items():
|
|
kwargs[key] = kwargs.get(key, value)
|
|
huddle = Huddle.objects.create(**kwargs)
|
|
recipient = Recipient.objects.create(type_id=huddle.id, type=Recipient.HUDDLE)
|
|
return huddle, recipient
|
|
|
|
def create_message(self, sender, recipient, **kwargs):
|
|
# type: (UserProfile, Recipient, **Any) -> Message
|
|
defaults = {
|
|
'sender': sender,
|
|
'recipient': recipient,
|
|
'subject': 'subject',
|
|
'content': 'hi',
|
|
'pub_date': self.TIME_LAST_HOUR,
|
|
'sending_client': get_client("website")}
|
|
for key, value in defaults.items():
|
|
kwargs[key] = kwargs.get(key, value)
|
|
return Message.objects.create(**kwargs)
|
|
|
|
# kwargs should only ever be a UserProfile or Stream.
|
|
def assertCountEquals(self, table, property, value, subgroup=None, end_time=TIME_ZERO,
|
|
interval=CountStat.HOUR, realm=None, **kwargs):
|
|
# type: (Type[BaseCount], Text, int, Optional[Text], datetime, str, Optional[Realm], **models.Model) -> None
|
|
queryset = table.objects.filter(property=property, interval=interval, end_time=end_time) \
|
|
.filter(**kwargs)
|
|
if table is not InstallationCount:
|
|
if realm is None:
|
|
realm = self.default_realm
|
|
queryset = queryset.filter(realm=realm)
|
|
if subgroup is not None:
|
|
queryset = queryset.filter(subgroup=subgroup)
|
|
self.assertEqual(queryset.values_list('value', flat=True)[0], value)
|
|
|
|
class TestProcessCountStat(AnalyticsTestCase):
|
|
def make_dummy_count_stat(self, current_time):
|
|
# type: (datetime) -> CountStat
|
|
dummy_query = """INSERT INTO analytics_realmcount (realm_id, property, end_time, interval, value)
|
|
VALUES (1, 'test stat', '%(end_time)s','hour', 22)""" % {'end_time': current_time}
|
|
count_stat = CountStat('test stat', ZerverCountQuery(Recipient, UserCount, dummy_query),
|
|
{}, None, CountStat.HOUR, False)
|
|
return count_stat
|
|
|
|
def assertFillStateEquals(self, end_time, state = FillState.DONE, property = None):
|
|
# type: (datetime, int, Optional[Text]) -> None
|
|
count_stat = self.make_dummy_count_stat(end_time)
|
|
if property is None:
|
|
property = count_stat.property
|
|
fill_state = FillState.objects.filter(property=property).first()
|
|
self.assertEqual(fill_state.end_time, end_time)
|
|
self.assertEqual(fill_state.state, state)
|
|
|
|
def test_process_stat(self):
|
|
# type: () -> None
|
|
# process new stat
|
|
current_time = installation_epoch() + self.HOUR
|
|
count_stat = self.make_dummy_count_stat(current_time)
|
|
process_count_stat(count_stat, current_time)
|
|
self.assertFillStateEquals(current_time)
|
|
self.assertEqual(InstallationCount.objects.filter(property = count_stat.property,
|
|
interval = CountStat.HOUR).count(), 1)
|
|
|
|
# dirty stat
|
|
FillState.objects.filter(property=count_stat.property).update(state=FillState.STARTED)
|
|
process_count_stat(count_stat, current_time)
|
|
self.assertFillStateEquals(current_time)
|
|
self.assertEqual(InstallationCount.objects.filter(property = count_stat.property,
|
|
interval = CountStat.HOUR).count(), 1)
|
|
|
|
# clean stat, no update
|
|
process_count_stat(count_stat, current_time)
|
|
self.assertFillStateEquals(current_time)
|
|
self.assertEqual(InstallationCount.objects.filter(property = count_stat.property,
|
|
interval = CountStat.HOUR).count(), 1)
|
|
|
|
# clean stat, with update
|
|
current_time = current_time + self.HOUR
|
|
count_stat = self.make_dummy_count_stat(current_time)
|
|
process_count_stat(count_stat, current_time)
|
|
self.assertFillStateEquals(current_time)
|
|
self.assertEqual(InstallationCount.objects.filter(property = count_stat.property,
|
|
interval = CountStat.HOUR).count(), 2)
|
|
|
|
class TestCountStats(AnalyticsTestCase):
|
|
def setUp(self):
|
|
# type: () -> None
|
|
super(TestCountStats, self).setUp()
|
|
self.second_realm = Realm.objects.create(
|
|
string_id='second-realm', name='Second Realm',
|
|
domain='second.analytics', date_created=self.TIME_ZERO-2*self.DAY)
|
|
user = self.create_user(realm=self.second_realm)
|
|
stream, recipient = self.create_stream_with_recipient(realm=self.second_realm)
|
|
self.create_message(user, recipient)
|
|
|
|
future_user = self.create_user(realm=self.second_realm, date_joined=self.TIME_ZERO)
|
|
future_stream, future_recipient = self.create_stream_with_recipient(
|
|
realm=self.second_realm, date_created=self.TIME_ZERO)
|
|
self.create_message(future_user, future_recipient, pub_date=self.TIME_ZERO)
|
|
|
|
def test_active_users_by_is_bot(self):
|
|
# type: () -> None
|
|
property = 'active_users:is_bot'
|
|
stat = COUNT_STATS[property]
|
|
|
|
# To be included
|
|
self.create_user(is_bot=True)
|
|
self.create_user(is_bot=True, date_joined=self.TIME_ZERO-25*self.HOUR)
|
|
self.create_user(is_bot=False)
|
|
|
|
# To be excluded
|
|
self.create_user(is_active=False)
|
|
|
|
do_fill_count_stat_at_hour(stat, self.TIME_ZERO)
|
|
|
|
self.assertCountEquals(RealmCount, property, 2, subgroup='true', interval=stat.interval)
|
|
self.assertCountEquals(RealmCount, property, 1, subgroup='false', interval=stat.interval)
|
|
self.assertCountEquals(RealmCount, property, 1, subgroup='false', interval=stat.interval, realm=self.second_realm)
|
|
self.assertEqual(RealmCount.objects.count(), 3)
|
|
self.assertCountEquals(InstallationCount, property, 2, subgroup='true', interval=stat.interval)
|
|
self.assertCountEquals(InstallationCount, property, 2, subgroup='false', interval=stat.interval)
|
|
self.assertEqual(InstallationCount.objects.count(), 2)
|
|
self.assertFalse(UserCount.objects.exists())
|
|
self.assertFalse(StreamCount.objects.exists())
|