datatracker/ietf/group/tests.py
Ryan Cross b07d4dbebc
feat: add group leadership list (#8135)
* feat: add Group Leadership list

* fix: only offer export to staff

* fix: fix export button conditional

* fix: improve tests. black format

---------

Co-authored-by: Robert Sparks <rjsparks@nostrum.com>
2024-11-15 12:18:09 -06:00

374 lines
16 KiB
Python

# Copyright The IETF Trust 2013-2020, All Rights Reserved
# -*- coding: utf-8 -*-
import datetime
import json
import mock
from django.urls import reverse as urlreverse
from django.db.models import Q
from django.test import Client
from django.utils import timezone
import debug # pyflakes:ignore
from ietf.doc.factories import DocumentFactory, WgDraftFactory, EditorialDraftFactory
from ietf.doc.models import DocEvent, RelatedDocument, Document
from ietf.group.models import Role, Group
from ietf.group.utils import (
get_group_role_emails,
get_child_group_role_emails,
get_group_ad_emails,
get_group_email_aliases,
GroupAliasGenerator,
role_holder_emails,
)
from ietf.group.factories import GroupFactory, RoleFactory
from ietf.person.factories import PersonFactory, EmailFactory
from ietf.person.models import Email, Person
from ietf.utils.test_utils import login_testing_unauthorized, TestCase
class StreamTests(TestCase):
def test_streams(self):
r = self.client.get(urlreverse("ietf.group.views.streams"))
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Independent Submission Editor")
def test_stream_documents(self):
draft = DocumentFactory(type_id='draft',group__acronym='iab',states=[('draft','active')])
draft.stream_id = "iab"
draft.save_with_history([DocEvent.objects.create(doc=draft, rev=draft.rev, type="changed_stream", by=Person.objects.get(user__username="secretary"), desc="Test")])
r = self.client.get(urlreverse("ietf.group.views.stream_documents", kwargs=dict(acronym="iab")))
self.assertEqual(r.status_code, 200)
self.assertContains(r, draft.name)
EditorialDraftFactory() # Quick way to ensure RSWG exists.
r = self.client.get(urlreverse("ietf.group.views.stream_documents", kwargs=dict(acronym="editorial")))
self.assertRedirects(r, expected_url=urlreverse('ietf.group.views.group_documents',kwargs={"acronym":"rswg"}))
def test_stream_edit(self):
EmailFactory(address="ad2@ietf.org")
stream_acronym = "ietf"
url = urlreverse("ietf.group.views.stream_edit", kwargs=dict(acronym=stream_acronym))
login_testing_unauthorized(self, "secretary", url)
# get
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
r = self.client.post(url, dict(delegates="ad2@ietf.org"))
self.assertEqual(r.status_code, 302)
self.assertTrue(Role.objects.filter(name="delegate", group__acronym=stream_acronym, email__address="ad2@ietf.org"))
class GroupLeadershipTests(TestCase):
def test_leadership_wg(self):
# setup various group states
bof_role = RoleFactory(
group__type_id="wg", group__state_id="bof", name_id="chair"
)
proposed_role = RoleFactory(
group__type_id="wg", group__state_id="proposed", name_id="chair"
)
active_role = RoleFactory(
group__type_id="wg", group__state_id="active", name_id="chair"
)
conclude_role = RoleFactory(
group__type_id="wg", group__state_id="conclude", name_id="chair"
)
url = urlreverse(
"ietf.group.views.group_leadership", kwargs={"group_type": "wg"}
)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Group Leadership")
self.assertContains(r, bof_role.person.last_name())
self.assertContains(r, proposed_role.person.last_name())
self.assertContains(r, active_role.person.last_name())
self.assertNotContains(r, conclude_role.person.last_name())
def test_leadership_wg_csv(self):
url = urlreverse(
"ietf.group.views.group_leadership_csv", kwargs={"group_type": "wg"}
)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r["Content-Type"], "text/csv")
self.assertContains(r, "Chairman, Sops")
def test_leadership_rg(self):
role = RoleFactory(group__type_id="rg", name_id="chair")
url = urlreverse(
"ietf.group.views.group_leadership", kwargs={"group_type": "rg"}
)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertContains(r, "Group Leadership")
self.assertContains(r, role.person.last_name())
self.assertNotContains(r, "Chairman, Sops")
class GroupStatsTests(TestCase):
def setUp(self):
super().setUp()
a = WgDraftFactory()
b = WgDraftFactory()
RelatedDocument.objects.create(
source=a, target=b, relationship_id="refnorm"
)
def test_group_stats(self):
client = Client(Accept="application/json")
url = urlreverse("ietf.group.views.group_stats_data")
r = client.get(url)
self.assertTrue(r.status_code == 200, "Failed to receive group stats")
self.assertGreater(len(r.content), 0, "Group stats have no content")
try:
data = json.loads(r.content)
except Exception as e:
self.fail("JSON load failed: %s" % e)
ids = [d["id"] for d in data]
for doc in Document.objects.all():
self.assertIn(doc.name, ids)
class GroupDocDependencyTests(TestCase):
def setUp(self):
super().setUp()
a = WgDraftFactory()
b = WgDraftFactory()
RelatedDocument.objects.create(
source=a, target=b, relationship_id="refnorm"
)
def test_group_document_dependencies(self):
for group in Group.objects.filter(Q(type="wg") | Q(type="rg")):
client = Client(Accept="application/json")
for url in [
urlreverse(
"ietf.group.views.dependencies", kwargs=dict(acronym=group.acronym)
),
urlreverse(
"ietf.group.views.dependencies",
kwargs=dict(acronym=group.acronym, group_type=group.type_id),
),
]:
r = client.get(url)
self.assertTrue(
r.status_code == 200,
"Failed to receive a group document dependencies for group: %s"
% group.acronym,
)
self.assertGreater(
len(r.content),
0,
"Document dependencies for group %s has no content" % group.acronym,
)
try:
json.loads(r.content)
except Exception as e:
self.fail("JSON load failed: %s" % e)
class GenerateGroupAliasesTests(TestCase):
def test_generator_class(self):
"""The GroupAliasGenerator should generate the same lists as the old mgmt cmd"""
# clean out test fixture group roles we don't need for this test
Role.objects.filter(
group__acronym__in=["farfut", "iab", "ietf", "irtf", "ise", "ops", "rsab", "rsoc", "sops"]
).delete()
a_month_ago = timezone.now() - datetime.timedelta(30)
a_decade_ago = timezone.now() - datetime.timedelta(3650)
role1 = RoleFactory(name_id='ad', group__type_id='area', group__acronym='myth', group__state_id='active')
area = role1.group
ad = role1.person
mars = GroupFactory(type_id='wg', acronym='mars', parent=area)
marschair = PersonFactory(user__username='marschair')
mars.role_set.create(name_id='chair', person=marschair, email=marschair.email())
marssecr = PersonFactory(user__username='marssecr')
mars.role_set.create(name_id='secr', person=marssecr, email=marssecr.email())
ames = GroupFactory(type_id='wg', acronym='ames', parent=area)
ameschair = PersonFactory(user__username='ameschair')
ames.role_set.create(name_id='chair', person=ameschair, email=ameschair.email())
recent = GroupFactory(type_id='wg', acronym='recent', parent=area, state_id='conclude', time=a_month_ago)
recentchair = PersonFactory(user__username='recentchair')
recent.role_set.create(name_id='chair', person=recentchair, email=recentchair.email())
wayold = GroupFactory(type_id='wg', acronym='wayold', parent=area, state_id='conclude', time=a_decade_ago)
wayoldchair = PersonFactory(user__username='wayoldchair')
wayold.role_set.create(name_id='chair', person=wayoldchair, email=wayoldchair.email())
# create a "done" group that should not be included anywhere
RoleFactory(name_id='ad', group__type_id='area', group__acronym='done', group__state_id='conclude')
irtf = Group.objects.get(acronym='irtf')
testrg = GroupFactory(type_id='rg', acronym='testrg', parent=irtf)
testrgchair = PersonFactory(user__username='testrgchair')
testrg.role_set.create(name_id='chair', person=testrgchair, email=testrgchair.email())
testrag = GroupFactory(type_id='rg', acronym='testrag', parent=irtf)
testragchair = PersonFactory(user__username='testragchair')
testrag.role_set.create(name_id='chair', person=testragchair, email=testragchair.email())
output = [(alias, (domains, alist)) for alias, domains, alist in GroupAliasGenerator()]
alias_dict = dict(output)
self.maxDiff = None
self.assertEqual(len(alias_dict), len(output)) # no duplicate aliases
expected_dict = {
area.acronym + "-ads": (["ietf"], [ad.email_address()]),
area.acronym + "-chairs": (["ietf"], [ad.email_address(), marschair.email_address(), marssecr.email_address(), ameschair.email_address()]),
mars.acronym + "-ads": (["ietf"], [ad.email_address()]),
mars.acronym + "-chairs": (["ietf"], [marschair.email_address(), marssecr.email_address()]),
ames.acronym + "-ads": (["ietf"], [ad.email_address()]),
ames.acronym + "-chairs": (["ietf"], [ameschair.email_address()]),
recent.acronym + "-ads": (["ietf"], [ad.email_address()]),
recent.acronym + "-chairs": (["ietf"], [recentchair.email_address()]),
testrg.acronym + "-chairs": (["ietf", "irtf"], [testrgchair.email_address()]),
testrag.acronym + "-chairs": (["ietf", "irtf"], [testragchair.email_address()]),
}
# Sort lists for comparison
self.assertEqual(
{k: (sorted(doms), sorted(addrs)) for k, (doms, addrs) in alias_dict.items()},
{k: (sorted(doms), sorted(addrs)) for k, (doms, addrs) in expected_dict.items()},
)
@mock.patch("ietf.group.utils.GroupAliasGenerator")
def test_get_group_email_aliases(self, mock_alias_gen_cls):
GroupFactory(name="agroup", type_id="rg")
GroupFactory(name="bgroup")
GroupFactory(name="cgroup", type_id="rg")
GroupFactory(name="dgroup")
mock_alias_gen_cls.return_value = [
("bgroup-chairs", ["ietf"], ["c1@example.com", "c2@example.com"]),
("agroup-ads", ["ietf", "irtf"], ["ad@example.com"]),
("bgroup-ads", ["ietf"], ["ad@example.com"]),
]
# order is important - should be by acronym, otherwise left in order returned by generator
self.assertEqual(
get_group_email_aliases(None, None),
[
{
"acronym": "agroup",
"alias_type": "-ads",
"expansion": "ad@example.com",
},
{
"acronym": "bgroup",
"alias_type": "-chairs",
"expansion": "c1@example.com, c2@example.com",
},
{
"acronym": "bgroup",
"alias_type": "-ads",
"expansion": "ad@example.com",
},
],
)
self.assertQuerySetEqual(
mock_alias_gen_cls.call_args[0][0],
Group.objects.all(),
ordered=False,
)
# test other parameter combinations but we already checked that the alias generator's
# output will be passed through, so don't re-test the processing
get_group_email_aliases("agroup", None)
self.assertQuerySetEqual(
mock_alias_gen_cls.call_args[0][0],
Group.objects.filter(acronym="agroup"),
ordered=False,
)
get_group_email_aliases(None, "wg")
self.assertQuerySetEqual(
mock_alias_gen_cls.call_args[0][0],
Group.objects.filter(type_id="wg"),
ordered=False,
)
get_group_email_aliases("agroup", "wg")
self.assertQuerySetEqual(
mock_alias_gen_cls.call_args[0][0],
Group.objects.none(),
ordered=False,
)
class GroupRoleEmailTests(TestCase):
def setUp(self):
super().setUp()
# make_immutable_base_data makes two areas, and puts a group in one of them
# the tests below assume all areas have groups
for area in Group.objects.filter(type_id='area'):
for iter_count in range(2):
group = GroupFactory(type_id='wg',parent=area)
RoleFactory(group=group,name_id='chair',person__user__email='{%s}chairman@ietf.org'%group.acronym)
RoleFactory(group=group,name_id='secr',person__user__email='{%s}secretary@ietf.org'%group.acronym)
def test_group_role_emails(self):
wgs = Group.objects.filter(type='wg')
for wg in wgs:
chair_emails = get_group_role_emails(wg, ['chair'])
secr_emails = get_group_role_emails(wg, ['secr'])
self.assertIn("chairman", list(chair_emails)[0])
self.assertIn("secretary", list(secr_emails)[0])
both_emails = get_group_role_emails(wg, ['chair', 'secr'])
self.assertEqual(secr_emails | chair_emails, both_emails)
def test_child_group_role_emails(self):
areas = Group.objects.filter(type='area')
for area in areas:
emails = get_child_group_role_emails(area, ['chair', 'secr'])
self.assertGreater(len(emails), 0)
for item in emails:
self.assertIn('@', item)
def test_group_ad_emails(self):
wgs = Group.objects.filter(type='wg')
for wg in wgs:
emails = get_group_ad_emails(wg)
self.assertGreater(len(emails), 0)
for item in emails:
self.assertIn('@', item)
def test_role_holder_emails(self):
# The test fixtures create a bunch of addresses that pollute this test's results - disable them
Email.objects.update(active=False)
role_holders = [
RoleFactory(name_id="member", group__type_id=gt).person
for gt in [
"ag",
"area",
"dir",
"iab",
"ietf",
"irtf",
"nomcom",
"rg",
"team",
"wg",
"rag",
]
]
# Expect an additional active email to be included
EmailFactory(
person=role_holders[0],
active=True,
)
# Do not expect an inactive email to be included
EmailFactory(
person=role_holders[1],
active=False,
)
# Do not expect address on a role-holder for a different group type
RoleFactory(name_id="member", group__type_id="adhoc") # arbitrary type not in the of-interest list
self.assertCountEqual(
role_holder_emails(),
Email.objects.filter(active=True, person__in=role_holders),
)