288 lines
13 KiB
Python
288 lines
13 KiB
Python
# Copyright The IETF Trust 2020, All Rights Reserved
|
|
import datetime
|
|
|
|
from django.db import IntegrityError
|
|
|
|
from ietf.group.factories import GroupFactory, RoleFactory
|
|
from ietf.name.models import DocTagName
|
|
from ietf.person.factories import PersonFactory
|
|
from ietf.utils.test_utils import TestCase
|
|
from ietf.person.models import Person
|
|
from ietf.doc.factories import DocumentFactory, WgRfcFactory
|
|
from ietf.doc.models import State, DocumentActionHolder, DocumentAuthor, Document
|
|
from ietf.doc.utils import update_action_holders, add_state_change_event, update_documentauthors, fuzzy_find_documents
|
|
|
|
|
|
class ActionHoldersTests(TestCase):
|
|
|
|
def setUp(self):
|
|
"""Set up helper for the update_action_holders tests"""
|
|
super().setUp()
|
|
self.authors = PersonFactory.create_batch(3)
|
|
self.ad = Person.objects.get(user__username='ad')
|
|
self.group = GroupFactory()
|
|
RoleFactory(name_id='ad', group=self.group, person=self.ad)
|
|
|
|
def doc_in_iesg_state(self, slug):
|
|
return DocumentFactory(authors=self.authors, group=self.group, ad=self.ad, states=[('draft-iesg', slug)])
|
|
|
|
def update_doc_state(self, doc, new_state, add_tags=None, remove_tags=None):
|
|
"""Update document state/tags, create change event, and save"""
|
|
prev_tags = list(doc.tags.all()) # list to make sure we retrieve now
|
|
# prev_action_holders = list(doc.action_holders.all())
|
|
|
|
prev_state = doc.get_state(new_state.type_id)
|
|
if new_state != prev_state:
|
|
doc.set_state(new_state)
|
|
|
|
if add_tags:
|
|
doc.tags.add(*DocTagName.objects.filter(slug__in=add_tags))
|
|
if remove_tags:
|
|
doc.tags.remove(*DocTagName.objects.filter(slug__in=remove_tags))
|
|
new_tags = list(doc.tags.all())
|
|
|
|
events = []
|
|
e = add_state_change_event(
|
|
doc,
|
|
Person.objects.get(name='(System)'),
|
|
prev_state, new_state,
|
|
prev_tags, new_tags)
|
|
self.assertIsNotNone(e, 'Test logic error')
|
|
events.append(e)
|
|
e = update_action_holders(doc, prev_state, new_state, prev_tags, new_tags)
|
|
if e:
|
|
events.append(e)
|
|
doc.save_with_history(events)
|
|
|
|
|
|
def test_update_action_holders_by_state(self):
|
|
"""Doc action holders should auto-update correctly on state change"""
|
|
# Test the transition from every state to each of its 'next_states'
|
|
|
|
for initial_state in State.objects.filter(type__slug='draft-iesg'):
|
|
for next_state in initial_state.next_states.all():
|
|
# Test with no action holders initially
|
|
doc = DocumentFactory(
|
|
authors=self.authors,
|
|
group=self.group,
|
|
ad=self.ad,
|
|
states=[('draft-iesg', initial_state.slug)],
|
|
)
|
|
docevents_before = set(doc.docevent_set.all())
|
|
|
|
self.update_doc_state(doc, next_state)
|
|
|
|
new_docevents = set(doc.docevent_set.all()).difference(docevents_before)
|
|
self.assertIn(doc.latest_event(type='changed_state'), new_docevents)
|
|
|
|
if next_state.slug in DocumentActionHolder.CLEAR_ACTION_HOLDERS_STATES:
|
|
self.assertCountEqual(doc.action_holders.all(), [])
|
|
self.assertEqual(len(new_docevents), 1)
|
|
else:
|
|
self.assertCountEqual(
|
|
doc.action_holders.all(), [doc.ad],
|
|
'AD should be only action holder after transition to %s' % next_state.slug)
|
|
|
|
self.assertEqual(len(new_docevents), 2)
|
|
change_event = doc.latest_event(type='changed_action_holders')
|
|
self.assertIn(change_event, new_docevents)
|
|
self.assertIn('Changed action holders', change_event.desc)
|
|
self.assertIn(doc.ad.name, change_event.desc)
|
|
doc.delete() # clean up for next iteration
|
|
|
|
# Test with action holders initially
|
|
doc = DocumentFactory(
|
|
authors=self.authors,
|
|
group=self.group,
|
|
ad=self.ad,
|
|
states=[('draft-iesg', initial_state.slug)],
|
|
)
|
|
doc.action_holders.add(*self.authors) # adds all authors
|
|
docevents_before = set(doc.docevent_set.all())
|
|
|
|
self.update_doc_state(doc, next_state)
|
|
|
|
new_docevents = set(doc.docevent_set.all()).difference(docevents_before)
|
|
self.assertEqual(len(new_docevents), 2)
|
|
self.assertIn(doc.latest_event(type='changed_state'), new_docevents)
|
|
change_event = doc.latest_event(type='changed_action_holders')
|
|
self.assertIn(change_event, new_docevents)
|
|
|
|
if next_state.slug in DocumentActionHolder.CLEAR_ACTION_HOLDERS_STATES:
|
|
self.assertCountEqual(doc.action_holders.all(), [])
|
|
self.assertIn('Removed all action holders', change_event.desc)
|
|
else:
|
|
self.assertCountEqual(
|
|
doc.action_holders.all(), [doc.ad],
|
|
'AD should be only action holder after transition to %s' % next_state.slug)
|
|
self.assertIn('Changed action holders', change_event.desc)
|
|
self.assertIn(doc.ad.name, change_event.desc)
|
|
doc.delete() # clean up for next iteration
|
|
|
|
def test_update_action_holders_with_no_ad(self):
|
|
"""A document with no AD should be handled gracefully"""
|
|
doc = self.doc_in_iesg_state('idexists')
|
|
doc.ad = None
|
|
doc.save()
|
|
|
|
docevents_before = set(doc.docevent_set.all())
|
|
self.update_doc_state(doc, State.objects.get(slug='pub-req'))
|
|
new_docevents = set(doc.docevent_set.all()).difference(docevents_before)
|
|
self.assertEqual(len(new_docevents), 1)
|
|
self.assertIn(doc.latest_event(type='changed_state'), new_docevents)
|
|
self.assertCountEqual(doc.action_holders.all(), [])
|
|
|
|
def test_update_action_holders_resets_age(self):
|
|
"""Action holder age should reset when document state changes"""
|
|
doc = self.doc_in_iesg_state('pub-req')
|
|
doc.action_holders.set([self.ad])
|
|
dah = doc.documentactionholder_set.get(person=self.ad)
|
|
dah.time_added = datetime.datetime(2020, 1, 1) # arbitrary date in the past
|
|
dah.save()
|
|
|
|
self.assertNotEqual(doc.documentactionholder_set.get(person=self.ad).time_added.date(), datetime.date.today())
|
|
self.update_doc_state(doc, State.objects.get(slug='ad-eval'))
|
|
self.assertEqual(doc.documentactionholder_set.get(person=self.ad).time_added.date(), datetime.date.today())
|
|
|
|
def test_update_action_holders_add_tag_need_rev(self):
|
|
"""Adding need-rev tag adds authors as action holders"""
|
|
doc = self.doc_in_iesg_state('pub-req')
|
|
first_author = self.authors[0]
|
|
doc.action_holders.add(first_author)
|
|
self.assertCountEqual(doc.action_holders.all(), [first_author])
|
|
self.update_doc_state(doc,
|
|
doc.get_state('draft-iesg'),
|
|
add_tags=['need-rev'],
|
|
remove_tags=None)
|
|
self.assertCountEqual(doc.action_holders.all(), self.authors)
|
|
|
|
def test_update_action_holders_add_tag_need_rev_no_dups(self):
|
|
"""Adding need-rev tag does not duplicate existing action holders"""
|
|
doc = self.doc_in_iesg_state('pub-req')
|
|
self.assertCountEqual(doc.action_holders.all(), [])
|
|
self.update_doc_state(doc,
|
|
doc.get_state('draft-iesg'),
|
|
add_tags=['need-rev'],
|
|
remove_tags=None)
|
|
self.assertCountEqual(doc.action_holders.all(), self.authors)
|
|
|
|
def test_update_action_holders_remove_tag_need_rev(self):
|
|
"""Removing need-rev tag drops authors as action holders"""
|
|
doc = self.doc_in_iesg_state('pub-req')
|
|
doc.tags.add(DocTagName.objects.get(slug='need-rev'))
|
|
self.assertEqual(doc.action_holders.count(), 0)
|
|
self.update_doc_state(doc,
|
|
doc.get_state('draft-iesg'),
|
|
add_tags=None,
|
|
remove_tags=['need-rev'])
|
|
self.assertEqual(doc.action_holders.count(), 0)
|
|
|
|
def test_update_action_holders_add_tag_need_rev_ignores_non_authors(self):
|
|
"""Adding need-rev tag does not affect existing action holders"""
|
|
doc = self.doc_in_iesg_state('pub-req')
|
|
doc.action_holders.add(self.ad)
|
|
self.assertCountEqual(doc.action_holders.all(),[self.ad])
|
|
self.update_doc_state(doc,
|
|
doc.get_state('draft-iesg'),
|
|
add_tags=['need-rev'],
|
|
remove_tags=None)
|
|
self.assertCountEqual(doc.action_holders.all(), [self.ad] + self.authors)
|
|
|
|
def test_update_action_holders_remove_tag_need_rev_ignores_non_authors(self):
|
|
"""Removing need-rev tag does not affect non-author action holders"""
|
|
doc = self.doc_in_iesg_state('pub-req')
|
|
doc.tags.add(DocTagName.objects.get(slug='need-rev'))
|
|
doc.action_holders.add(self.ad)
|
|
self.assertCountEqual(doc.action_holders.all(), [self.ad])
|
|
self.update_doc_state(doc,
|
|
doc.get_state('draft-iesg'),
|
|
add_tags=None,
|
|
remove_tags=['need-rev'])
|
|
self.assertCountEqual(doc.action_holders.all(), [self.ad])
|
|
|
|
def test_doc_action_holders_enabled(self):
|
|
"""Action holders should only be enabled in certain states"""
|
|
doc = self.doc_in_iesg_state('idexists')
|
|
self.assertFalse(doc.action_holders_enabled())
|
|
|
|
for state in State.objects.filter(type='draft-iesg').exclude(slug='idexists'):
|
|
doc.set_state(state)
|
|
self.assertTrue(doc.action_holders_enabled())
|
|
|
|
|
|
class MiscTests(TestCase):
|
|
def test_update_documentauthors_with_nulls(self):
|
|
"""A 'None' value in the affiliation/country should be handled correctly"""
|
|
author_person = PersonFactory()
|
|
doc = DocumentFactory(authors=[author_person])
|
|
doc.documentauthor_set.update(
|
|
affiliation='Some Affiliation', country='USA'
|
|
)
|
|
try:
|
|
events = update_documentauthors(
|
|
doc,
|
|
[
|
|
DocumentAuthor(
|
|
person=author_person,
|
|
email=author_person.email(),
|
|
affiliation=None,
|
|
country=None,
|
|
)
|
|
],
|
|
)
|
|
except IntegrityError as err:
|
|
self.fail('IntegrityError was raised: {}'.format(err))
|
|
|
|
self.assertEqual(len(events), 1)
|
|
self.assertEqual(events[0].type, 'edited_authors')
|
|
self.assertIn('cleared affiliation (was "Some Affiliation")', events[0].desc)
|
|
self.assertIn('cleared country (was "USA")', events[0].desc)
|
|
docauth = doc.documentauthor_set.first()
|
|
self.assertEqual(docauth.affiliation, '')
|
|
self.assertEqual(docauth.country, '')
|
|
|
|
def do_fuzzy_find_documents_rfc_test(self, name):
|
|
rfc = WgRfcFactory(name=name, create_revisions=(0, 1, 2))
|
|
rfc = Document.objects.get(pk=rfc.pk) # clear out any cached values
|
|
|
|
# by canonical name
|
|
found = fuzzy_find_documents(rfc.canonical_name(), None)
|
|
self.assertCountEqual(found.documents, [rfc])
|
|
self.assertEqual(found.matched_rev, None)
|
|
self.assertEqual(found.matched_name, rfc.canonical_name())
|
|
|
|
# by draft name, no rev
|
|
found = fuzzy_find_documents(rfc.name, None)
|
|
self.assertCountEqual(found.documents, [rfc])
|
|
self.assertEqual(found.matched_rev, None)
|
|
self.assertEqual(found.matched_name, rfc.name)
|
|
|
|
# by draft name, latest rev
|
|
found = fuzzy_find_documents(rfc.name, '02')
|
|
self.assertCountEqual(found.documents, [rfc])
|
|
self.assertEqual(found.matched_rev, '02')
|
|
self.assertEqual(found.matched_name, rfc.name)
|
|
|
|
# by draft name, earlier rev
|
|
found = fuzzy_find_documents(rfc.name, '01')
|
|
self.assertCountEqual(found.documents, [rfc])
|
|
self.assertEqual(found.matched_rev, '01')
|
|
self.assertEqual(found.matched_name, rfc.name)
|
|
|
|
# wrong name or revision
|
|
found = fuzzy_find_documents(rfc.name + '-incorrect')
|
|
self.assertCountEqual(found.documents, [], 'Should not find document that does not match')
|
|
found = fuzzy_find_documents(rfc.name + '-incorrect', '02')
|
|
self.assertCountEqual(found.documents, [], 'Still should not find document, even with a version')
|
|
found = fuzzy_find_documents(rfc.name, '22')
|
|
self.assertCountEqual(found.documents, [rfc],
|
|
'Should find document even if rev does not exist')
|
|
|
|
|
|
def test_fuzzy_find_documents(self):
|
|
# Should add additional tests/test cases for other document types/name formats
|
|
self.do_fuzzy_find_documents_rfc_test('draft-normal-name')
|
|
self.do_fuzzy_find_documents_rfc_test('draft-name-with-number-01')
|
|
self.do_fuzzy_find_documents_rfc_test('draft-name-that-has-two-02-04')
|
|
self.do_fuzzy_find_documents_rfc_test('draft-wild-01-numbers-0312')
|