fix: Move rfcdiff-latest-json API call from /doc to /api (#5155)
Old /doc/rfcdiff-latest-json/ URL is changed to a permanent redirect to /api/doc/rfcdiff-latest-json/
This commit is contained in:
parent
81ffad9852
commit
f70ce3eaab
|
@ -23,6 +23,8 @@ import debug # pyflakes:ignore
|
|||
|
||||
import ietf
|
||||
from ietf.doc.utils import get_unicode_document_content
|
||||
from ietf.doc.models import RelatedDocument, State
|
||||
from ietf.doc.factories import IndividualDraftFactory, WgDraftFactory
|
||||
from ietf.group.factories import RoleFactory
|
||||
from ietf.meeting.factories import MeetingFactory, SessionFactory
|
||||
from ietf.meeting.test_data import make_meeting_test_data
|
||||
|
@ -33,7 +35,7 @@ from ietf.person.models import PersonalApiKey
|
|||
from ietf.stats.models import MeetingRegistration
|
||||
from ietf.utils.mail import outbox, get_payload_text
|
||||
from ietf.utils.models import DumpInfo
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, reload_db_objects
|
||||
|
||||
OMITTED_APPS = (
|
||||
'ietf.secr.meetings',
|
||||
|
@ -589,3 +591,198 @@ class TastypieApiTestCase(ResourceTestCaseMixin, TestCase):
|
|||
#print("There doesn't seem to be any resource for model %s.models.%s"%(app.__name__,model.__name__,))
|
||||
self.assertIn(model._meta.model_name, list(app_resources.keys()),
|
||||
"There doesn't seem to be any API resource for model %s.models.%s"%(app.__name__,model.__name__,))
|
||||
|
||||
|
||||
class RfcdiffSupportTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.target_view = 'ietf.api.views.rfcdiff_latest_json'
|
||||
self._last_rfc_num = 8000
|
||||
|
||||
def getJson(self, view_args):
|
||||
url = urlreverse(self.target_view, kwargs=view_args)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
return r.json()
|
||||
|
||||
def next_rfc_number(self):
|
||||
self._last_rfc_num += 1
|
||||
return self._last_rfc_num
|
||||
|
||||
def do_draft_test(self, name):
|
||||
draft = IndividualDraftFactory(name=name, rev='00', create_revisions=range(0,13))
|
||||
draft = reload_db_objects(draft)
|
||||
|
||||
received = self.getJson(dict(name=draft.name))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
name=draft.name,
|
||||
rev=draft.rev,
|
||||
content_url=draft.get_href(),
|
||||
previous=f'{draft.name}-{(int(draft.rev)-1):02d}'
|
||||
),
|
||||
'Incorrect JSON when draft revision not specified',
|
||||
)
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev=draft.rev))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
name=draft.name,
|
||||
rev=draft.rev,
|
||||
content_url=draft.get_href(),
|
||||
previous=f'{draft.name}-{(int(draft.rev)-1):02d}'
|
||||
),
|
||||
'Incorrect JSON when latest revision specified',
|
||||
)
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev='10'))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
name=draft.name,
|
||||
rev='10',
|
||||
content_url=draft.history_set.get(rev='10').get_href(),
|
||||
previous=f'{draft.name}-09'
|
||||
),
|
||||
'Incorrect JSON when historical revision specified',
|
||||
)
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev='00'))
|
||||
self.assertNotIn('previous', received, 'Rev 00 has no previous name when not replacing a draft')
|
||||
|
||||
replaced = IndividualDraftFactory()
|
||||
RelatedDocument.objects.create(relationship_id='replaces',source=draft,target=replaced.docalias.first())
|
||||
received = self.getJson(dict(name=draft.name, rev='00'))
|
||||
self.assertEqual(received['previous'], f'{replaced.name}-{replaced.rev}',
|
||||
'Rev 00 has a previous name when replacing a draft')
|
||||
|
||||
def test_draft(self):
|
||||
# test with typical, straightforward names
|
||||
self.do_draft_test(name='draft-somebody-did-a-thing')
|
||||
# try with different potentially problematic names
|
||||
self.do_draft_test(name='draft-someone-did-something-01-02')
|
||||
self.do_draft_test(name='draft-someone-did-something-else-02')
|
||||
self.do_draft_test(name='draft-someone-did-something-02-weird-01')
|
||||
|
||||
def do_draft_with_broken_history_test(self, name):
|
||||
draft = IndividualDraftFactory(name=name, rev='10')
|
||||
received = self.getJson(dict(name=draft.name,rev='09'))
|
||||
self.assertEqual(received['rev'],'09')
|
||||
self.assertEqual(received['previous'], f'{draft.name}-08')
|
||||
self.assertTrue('warning' in received)
|
||||
|
||||
def test_draft_with_broken_history(self):
|
||||
# test with typical, straightforward names
|
||||
self.do_draft_with_broken_history_test(name='draft-somebody-did-something')
|
||||
# try with different potentially problematic names
|
||||
self.do_draft_with_broken_history_test(name='draft-someone-did-something-01-02')
|
||||
self.do_draft_with_broken_history_test(name='draft-someone-did-something-else-02')
|
||||
self.do_draft_with_broken_history_test(name='draft-someone-did-something-02-weird-03')
|
||||
|
||||
def do_rfc_test(self, draft_name):
|
||||
draft = WgDraftFactory(name=draft_name, create_revisions=range(0,2))
|
||||
draft.docalias.create(name=f'rfc{self.next_rfc_number():04}')
|
||||
draft.set_state(State.objects.get(type_id='draft',slug='rfc'))
|
||||
draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
|
||||
draft = reload_db_objects(draft)
|
||||
rfc = draft
|
||||
|
||||
number = rfc.rfc_number()
|
||||
received = self.getJson(dict(name=number))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
content_url=rfc.get_href(),
|
||||
name=rfc.canonical_name(),
|
||||
previous=f'{draft.name}-{draft.rev}',
|
||||
),
|
||||
'Can look up an RFC by number',
|
||||
)
|
||||
|
||||
num_received = received
|
||||
received = self.getJson(dict(name=rfc.canonical_name()))
|
||||
self.assertEqual(num_received, received, 'RFC by canonical name gives same result as by number')
|
||||
|
||||
received = self.getJson(dict(name=f'RfC {number}'))
|
||||
self.assertEqual(num_received, received, 'RFC with unusual spacing/caps gives same result as by number')
|
||||
|
||||
received = self.getJson(dict(name=draft.name))
|
||||
self.assertEqual(num_received, received, 'RFC by draft name and no rev gives same result as by number')
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev='01'))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
content_url=draft.history_set.get(rev='01').get_href(),
|
||||
name=draft.name,
|
||||
rev='01',
|
||||
previous=f'{draft.name}-00',
|
||||
),
|
||||
'RFC by draft name with rev should give draft name, not canonical name'
|
||||
)
|
||||
|
||||
def test_rfc(self):
|
||||
# simple draft name
|
||||
self.do_rfc_test(draft_name='draft-test-ar-ef-see')
|
||||
# tricky draft names
|
||||
self.do_rfc_test(draft_name='draft-whatever-02')
|
||||
self.do_rfc_test(draft_name='draft-test-me-03-04')
|
||||
|
||||
def test_rfc_with_tombstone(self):
|
||||
draft = WgDraftFactory(create_revisions=range(0,2))
|
||||
draft.docalias.create(name='rfc3261') # See views_doc.HAS_TOMBSTONE
|
||||
draft.set_state(State.objects.get(type_id='draft',slug='rfc'))
|
||||
draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
|
||||
draft = reload_db_objects(draft)
|
||||
rfc = draft
|
||||
|
||||
# Some old rfcs had tombstones that shouldn't be used for comparisons
|
||||
received = self.getJson(dict(name=rfc.canonical_name()))
|
||||
self.assertTrue(received['previous'].endswith('00'))
|
||||
|
||||
def do_rfc_with_broken_history_test(self, draft_name):
|
||||
draft = WgDraftFactory(rev='10', name=draft_name)
|
||||
draft.docalias.create(name=f'rfc{self.next_rfc_number():04}')
|
||||
draft.set_state(State.objects.get(type_id='draft',slug='rfc'))
|
||||
draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
|
||||
draft = reload_db_objects(draft)
|
||||
rfc = draft
|
||||
|
||||
received = self.getJson(dict(name=draft.name))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
content_url=rfc.get_href(),
|
||||
name=rfc.canonical_name(),
|
||||
previous=f'{draft.name}-10',
|
||||
),
|
||||
'RFC by draft name without rev should return canonical RFC name and no rev',
|
||||
)
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev='10'))
|
||||
self.assertEqual(received['name'], draft.name, 'RFC by draft name with rev should return draft name')
|
||||
self.assertEqual(received['rev'], '10', 'Requested rev should be returned')
|
||||
self.assertEqual(received['previous'], f'{draft.name}-09', 'Previous rev is one less than requested')
|
||||
self.assertIn(f'{draft.name}-10', received['content_url'], 'Returned URL should include requested rev')
|
||||
self.assertNotIn('warning', received, 'No warning when we have the rev requested')
|
||||
|
||||
received = self.getJson(dict(name=f'{draft.name}-09'))
|
||||
self.assertEqual(received['name'], draft.name, 'RFC by draft name with rev should return draft name')
|
||||
self.assertEqual(received['rev'], '09', 'Requested rev should be returned')
|
||||
self.assertEqual(received['previous'], f'{draft.name}-08', 'Previous rev is one less than requested')
|
||||
self.assertIn(f'{draft.name}-09', received['content_url'], 'Returned URL should include requested rev')
|
||||
self.assertEqual(
|
||||
received['warning'],
|
||||
'History for this version not found - these results are speculation',
|
||||
'Warning should be issued when requested rev is not found'
|
||||
)
|
||||
|
||||
def test_rfc_with_broken_history(self):
|
||||
# simple draft name
|
||||
self.do_rfc_with_broken_history_test(draft_name='draft-some-draft')
|
||||
# tricky draft names
|
||||
self.do_rfc_with_broken_history_test(draft_name='draft-gizmo-01')
|
||||
self.do_rfc_with_broken_history_test(draft_name='draft-oh-boy-what-a-draft-02-03')
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright The IETF Trust 2017, All Rights Reserved
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
@ -56,6 +57,9 @@ urlpatterns = [
|
|||
url(r'^version/?$', api_views.version),
|
||||
# Application authentication API key
|
||||
url(r'^appauth/[authortools|bibxml]', api_views.app_auth),
|
||||
# latest versions
|
||||
url(r'^rfcdiff-latest-json/%(name)s(?:-%(rev)s)?(\.txt|\.html)?/?$' % settings.URL_REGEXPS, api_views.rfcdiff_latest_json),
|
||||
url(r'^rfcdiff-latest-json/(?P<name>[Rr][Ff][Cc] [0-9]+?)(\.txt|\.html)?/?$', api_views.rfcdiff_latest_json),
|
||||
]
|
||||
|
||||
# Additional (standard) Tastypie endpoints
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import json
|
||||
import pytz
|
||||
import re
|
||||
|
||||
from jwcrypto.jwk import JWK
|
||||
|
||||
|
@ -12,7 +13,7 @@ from django.contrib.auth.decorators import login_required
|
|||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
|
@ -31,10 +32,12 @@ import ietf
|
|||
from ietf.person.models import Person, Email
|
||||
from ietf.api import _api_list
|
||||
from ietf.api.serializer import JsonExportMixin
|
||||
from ietf.doc.utils import fuzzy_find_documents
|
||||
from ietf.ietfauth.views import send_account_creation_email
|
||||
from ietf.ietfauth.utils import role_required
|
||||
from ietf.meeting.models import Meeting
|
||||
from ietf.stats.models import MeetingRegistration
|
||||
from ietf.utils import log
|
||||
from ietf.utils.decorators import require_api_key
|
||||
from ietf.utils.models import DumpInfo
|
||||
|
||||
|
@ -225,3 +228,138 @@ def app_auth(request):
|
|||
return HttpResponse(
|
||||
json.dumps({'success': True}),
|
||||
content_type='application/json')
|
||||
|
||||
|
||||
|
||||
def find_doc_for_rfcdiff(name, rev):
|
||||
"""rfcdiff lookup heuristics
|
||||
|
||||
Returns a tuple with:
|
||||
[0] - condition string
|
||||
[1] - document found (or None)
|
||||
[2] - historic version
|
||||
[3] - revision actually found (may differ from :rev: input)
|
||||
"""
|
||||
found = fuzzy_find_documents(name, rev)
|
||||
condition = 'no such document'
|
||||
if found.documents.count() != 1:
|
||||
return (condition, None, None, rev)
|
||||
doc = found.documents.get()
|
||||
if found.matched_rev is None or doc.rev == found.matched_rev:
|
||||
condition = 'current version'
|
||||
return (condition, doc, None, found.matched_rev)
|
||||
else:
|
||||
candidate = doc.history_set.filter(rev=found.matched_rev).order_by("-time").first()
|
||||
if candidate:
|
||||
condition = 'historic version'
|
||||
return (condition, doc, candidate, found.matched_rev)
|
||||
else:
|
||||
condition = 'version dochistory not found'
|
||||
return (condition, doc, None, found.matched_rev)
|
||||
|
||||
# This is a proof of concept of a service that would redirect to the current revision
|
||||
# def rfcdiff_latest(request, name, rev=None):
|
||||
# condition, doc, history = find_doc_for_rfcdiff(name, rev)
|
||||
# if not doc:
|
||||
# raise Http404
|
||||
# if history:
|
||||
# return redirect(history.get_href())
|
||||
# else:
|
||||
# return redirect(doc.get_href())
|
||||
|
||||
HAS_TOMBSTONE = [
|
||||
2821, 2822, 2873, 2919, 2961, 3023, 3029, 3031, 3032, 3033, 3034, 3035, 3036,
|
||||
3037, 3038, 3042, 3044, 3050, 3052, 3054, 3055, 3056, 3057, 3059, 3060, 3061,
|
||||
3062, 3063, 3064, 3067, 3068, 3069, 3070, 3071, 3072, 3073, 3074, 3075, 3076,
|
||||
3077, 3078, 3080, 3081, 3082, 3084, 3085, 3086, 3087, 3088, 3089, 3090, 3094,
|
||||
3095, 3096, 3097, 3098, 3101, 3102, 3103, 3104, 3105, 3106, 3107, 3108, 3109,
|
||||
3110, 3111, 3112, 3113, 3114, 3115, 3116, 3117, 3118, 3119, 3120, 3121, 3123,
|
||||
3124, 3126, 3127, 3128, 3130, 3131, 3132, 3133, 3134, 3135, 3136, 3137, 3138,
|
||||
3139, 3140, 3141, 3142, 3143, 3144, 3145, 3147, 3149, 3150, 3151, 3152, 3153,
|
||||
3154, 3155, 3156, 3157, 3158, 3159, 3160, 3161, 3162, 3163, 3164, 3165, 3166,
|
||||
3167, 3168, 3169, 3170, 3171, 3172, 3173, 3174, 3176, 3179, 3180, 3181, 3182,
|
||||
3183, 3184, 3185, 3186, 3187, 3188, 3189, 3190, 3191, 3192, 3193, 3194, 3197,
|
||||
3198, 3201, 3202, 3203, 3204, 3205, 3206, 3207, 3208, 3209, 3210, 3211, 3212,
|
||||
3213, 3214, 3215, 3216, 3217, 3218, 3220, 3221, 3222, 3224, 3225, 3226, 3227,
|
||||
3228, 3229, 3230, 3231, 3232, 3233, 3234, 3235, 3236, 3237, 3238, 3240, 3241,
|
||||
3242, 3243, 3244, 3245, 3246, 3247, 3248, 3249, 3250, 3253, 3254, 3255, 3256,
|
||||
3257, 3258, 3259, 3260, 3261, 3262, 3263, 3264, 3265, 3266, 3267, 3268, 3269,
|
||||
3270, 3271, 3272, 3273, 3274, 3275, 3276, 3278, 3279, 3280, 3281, 3282, 3283,
|
||||
3284, 3285, 3286, 3287, 3288, 3289, 3290, 3291, 3292, 3293, 3294, 3295, 3296,
|
||||
3297, 3298, 3301, 3302, 3303, 3304, 3305, 3307, 3308, 3309, 3310, 3311, 3312,
|
||||
3313, 3315, 3317, 3318, 3319, 3320, 3321, 3322, 3323, 3324, 3325, 3326, 3327,
|
||||
3329, 3330, 3331, 3332, 3334, 3335, 3336, 3338, 3340, 3341, 3342, 3343, 3346,
|
||||
3348, 3349, 3351, 3352, 3353, 3354, 3355, 3356, 3360, 3361, 3362, 3363, 3364,
|
||||
3366, 3367, 3368, 3369, 3370, 3371, 3372, 3374, 3375, 3377, 3378, 3379, 3383,
|
||||
3384, 3385, 3386, 3387, 3388, 3389, 3390, 3391, 3394, 3395, 3396, 3397, 3398,
|
||||
3401, 3402, 3403, 3404, 3405, 3406, 3407, 3408, 3409, 3410, 3411, 3412, 3413,
|
||||
3414, 3415, 3416, 3417, 3418, 3419, 3420, 3421, 3422, 3423, 3424, 3425, 3426,
|
||||
3427, 3428, 3429, 3430, 3431, 3433, 3434, 3435, 3436, 3437, 3438, 3439, 3440,
|
||||
3441, 3443, 3444, 3445, 3446, 3447, 3448, 3449, 3450, 3451, 3452, 3453, 3454,
|
||||
3455, 3458, 3459, 3460, 3461, 3462, 3463, 3464, 3465, 3466, 3467, 3468, 3469,
|
||||
3470, 3471, 3472, 3473, 3474, 3475, 3476, 3477, 3480, 3481, 3483, 3485, 3488,
|
||||
3494, 3495, 3496, 3497, 3498, 3501, 3502, 3503, 3504, 3505, 3506, 3507, 3508,
|
||||
3509, 3511, 3512, 3515, 3516, 3517, 3518, 3520, 3521, 3522, 3523, 3524, 3525,
|
||||
3527, 3529, 3530, 3532, 3533, 3534, 3536, 3537, 3538, 3539, 3541, 3543, 3544,
|
||||
3545, 3546, 3547, 3548, 3549, 3550, 3551, 3552, 3555, 3556, 3557, 3558, 3559,
|
||||
3560, 3562, 3563, 3564, 3565, 3568, 3569, 3570, 3571, 3572, 3573, 3574, 3575,
|
||||
3576, 3577, 3578, 3579, 3580, 3581, 3582, 3583, 3584, 3588, 3589, 3590, 3591,
|
||||
3592, 3593, 3594, 3595, 3597, 3598, 3601, 3607, 3609, 3610, 3612, 3614, 3615,
|
||||
3616, 3625, 3627, 3630, 3635, 3636, 3637, 3638
|
||||
]
|
||||
|
||||
def rfcdiff_latest_json(request, name, rev=None):
|
||||
response = dict()
|
||||
condition, document, history, found_rev = find_doc_for_rfcdiff(name, rev)
|
||||
|
||||
if condition == 'no such document':
|
||||
raise Http404
|
||||
elif condition in ('historic version', 'current version'):
|
||||
doc = history if history else document
|
||||
if not found_rev and doc.is_rfc():
|
||||
response['content_url'] = doc.get_href()
|
||||
response['name']=doc.canonical_name()
|
||||
if doc.name != doc.canonical_name():
|
||||
prev_rev = doc.rev
|
||||
# not sure what to do if non-numeric values come back, so at least log it
|
||||
log.assertion('doc.rfc_number().isdigit()') # .rfc_number() is expensive...
|
||||
log.assertion('doc.rev.isdigit()')
|
||||
if int(doc.rfc_number()) in HAS_TOMBSTONE and prev_rev != '00':
|
||||
prev_rev = f'{(int(doc.rev)-1):02d}'
|
||||
response['previous'] = f'{doc.name}-{prev_rev}'
|
||||
else:
|
||||
doc.is_rfc = lambda: False
|
||||
response['content_url'] = doc.get_href()
|
||||
response['rev'] = doc.rev
|
||||
response['name'] = doc.name
|
||||
if doc.rev == '00':
|
||||
replaces_docs = (history.doc if condition=='historic version' else doc).related_that_doc('replaces')
|
||||
if replaces_docs:
|
||||
replaces = replaces_docs[0].document
|
||||
response['previous'] = f'{replaces.name}-{replaces.rev}'
|
||||
else:
|
||||
match = re.search("-(rfc)?([0-9][0-9][0-9]+)bis(-.*)?$", name)
|
||||
if match and match.group(2):
|
||||
response['previous'] = f'rfc{match.group(2)}'
|
||||
else:
|
||||
# not sure what to do if non-numeric values come back, so at least log it
|
||||
log.assertion('doc.rev.isdigit()')
|
||||
response['previous'] = f'{doc.name}-{(int(doc.rev)-1):02d}'
|
||||
elif condition == 'version dochistory not found':
|
||||
response['warning'] = 'History for this version not found - these results are speculation'
|
||||
response['name'] = document.name
|
||||
response['rev'] = found_rev
|
||||
document.rev = found_rev
|
||||
document.is_rfc = lambda: False
|
||||
response['content_url'] = document.get_href()
|
||||
# not sure what to do if non-numeric values come back, so at least log it
|
||||
log.assertion('found_rev.isdigit()')
|
||||
if int(found_rev) > 0:
|
||||
response['previous'] = f'{document.name}-{(int(found_rev)-1):02d}'
|
||||
else:
|
||||
match = re.search("-(rfc)?([0-9][0-9][0-9]+)bis(-.*)?$", name)
|
||||
if match and match.group(2):
|
||||
response['previous'] = f'rfc{match.group(2)}'
|
||||
if not response:
|
||||
raise Http404
|
||||
return HttpResponse(json.dumps(response), content_type='application/json')
|
||||
|
|
|
@ -56,7 +56,7 @@ from ietf.name.models import SessionStatusName, BallotPositionName, DocTypeName
|
|||
from ietf.person.models import Person
|
||||
from ietf.person.factories import PersonFactory, EmailFactory
|
||||
from ietf.utils.mail import outbox, empty_outbox
|
||||
from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects
|
||||
from ietf.utils.test_utils import login_testing_unauthorized, unicontent
|
||||
from ietf.utils.test_utils import TestCase
|
||||
from ietf.utils.text import normalize_text
|
||||
from ietf.utils.timezone import date_today, datetime_today, DEADLINE_TZINFO, RPC_TZINFO
|
||||
|
@ -2662,200 +2662,6 @@ class Idnits2SupportTests(TestCase):
|
|||
self.assertEqual(r.status_code, 200)
|
||||
self.assertContains(r,'Proposed')
|
||||
|
||||
class RfcdiffSupportTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.target_view = 'ietf.doc.views_doc.rfcdiff_latest_json'
|
||||
self._last_rfc_num = 8000
|
||||
|
||||
def getJson(self, view_args):
|
||||
url = urlreverse(self.target_view, kwargs=view_args)
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
return r.json()
|
||||
|
||||
def next_rfc_number(self):
|
||||
self._last_rfc_num += 1
|
||||
return self._last_rfc_num
|
||||
|
||||
def do_draft_test(self, name):
|
||||
draft = IndividualDraftFactory(name=name, rev='00', create_revisions=range(0,13))
|
||||
draft = reload_db_objects(draft)
|
||||
|
||||
received = self.getJson(dict(name=draft.name))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
name=draft.name,
|
||||
rev=draft.rev,
|
||||
content_url=draft.get_href(),
|
||||
previous=f'{draft.name}-{(int(draft.rev)-1):02d}'
|
||||
),
|
||||
'Incorrect JSON when draft revision not specified',
|
||||
)
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev=draft.rev))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
name=draft.name,
|
||||
rev=draft.rev,
|
||||
content_url=draft.get_href(),
|
||||
previous=f'{draft.name}-{(int(draft.rev)-1):02d}'
|
||||
),
|
||||
'Incorrect JSON when latest revision specified',
|
||||
)
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev='10'))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
name=draft.name,
|
||||
rev='10',
|
||||
content_url=draft.history_set.get(rev='10').get_href(),
|
||||
previous=f'{draft.name}-09'
|
||||
),
|
||||
'Incorrect JSON when historical revision specified',
|
||||
)
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev='00'))
|
||||
self.assertNotIn('previous', received, 'Rev 00 has no previous name when not replacing a draft')
|
||||
|
||||
replaced = IndividualDraftFactory()
|
||||
RelatedDocument.objects.create(relationship_id='replaces',source=draft,target=replaced.docalias.first())
|
||||
received = self.getJson(dict(name=draft.name, rev='00'))
|
||||
self.assertEqual(received['previous'], f'{replaced.name}-{replaced.rev}',
|
||||
'Rev 00 has a previous name when replacing a draft')
|
||||
|
||||
def test_draft(self):
|
||||
# test with typical, straightforward names
|
||||
self.do_draft_test(name='draft-somebody-did-a-thing')
|
||||
# try with different potentially problematic names
|
||||
self.do_draft_test(name='draft-someone-did-something-01-02')
|
||||
self.do_draft_test(name='draft-someone-did-something-else-02')
|
||||
self.do_draft_test(name='draft-someone-did-something-02-weird-01')
|
||||
|
||||
def do_draft_with_broken_history_test(self, name):
|
||||
draft = IndividualDraftFactory(name=name, rev='10')
|
||||
received = self.getJson(dict(name=draft.name,rev='09'))
|
||||
self.assertEqual(received['rev'],'09')
|
||||
self.assertEqual(received['previous'], f'{draft.name}-08')
|
||||
self.assertTrue('warning' in received)
|
||||
|
||||
def test_draft_with_broken_history(self):
|
||||
# test with typical, straightforward names
|
||||
self.do_draft_with_broken_history_test(name='draft-somebody-did-something')
|
||||
# try with different potentially problematic names
|
||||
self.do_draft_with_broken_history_test(name='draft-someone-did-something-01-02')
|
||||
self.do_draft_with_broken_history_test(name='draft-someone-did-something-else-02')
|
||||
self.do_draft_with_broken_history_test(name='draft-someone-did-something-02-weird-03')
|
||||
|
||||
def do_rfc_test(self, draft_name):
|
||||
draft = WgDraftFactory(name=draft_name, create_revisions=range(0,2))
|
||||
draft.docalias.create(name=f'rfc{self.next_rfc_number():04}')
|
||||
draft.set_state(State.objects.get(type_id='draft',slug='rfc'))
|
||||
draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
|
||||
draft = reload_db_objects(draft)
|
||||
rfc = draft
|
||||
|
||||
number = rfc.rfc_number()
|
||||
received = self.getJson(dict(name=number))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
content_url=rfc.get_href(),
|
||||
name=rfc.canonical_name(),
|
||||
previous=f'{draft.name}-{draft.rev}',
|
||||
),
|
||||
'Can look up an RFC by number',
|
||||
)
|
||||
|
||||
num_received = received
|
||||
received = self.getJson(dict(name=rfc.canonical_name()))
|
||||
self.assertEqual(num_received, received, 'RFC by canonical name gives same result as by number')
|
||||
|
||||
received = self.getJson(dict(name=f'RfC {number}'))
|
||||
self.assertEqual(num_received, received, 'RFC with unusual spacing/caps gives same result as by number')
|
||||
|
||||
received = self.getJson(dict(name=draft.name))
|
||||
self.assertEqual(num_received, received, 'RFC by draft name and no rev gives same result as by number')
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev='01'))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
content_url=draft.history_set.get(rev='01').get_href(),
|
||||
name=draft.name,
|
||||
rev='01',
|
||||
previous=f'{draft.name}-00',
|
||||
),
|
||||
'RFC by draft name with rev should give draft name, not canonical name'
|
||||
)
|
||||
|
||||
def test_rfc(self):
|
||||
# simple draft name
|
||||
self.do_rfc_test(draft_name='draft-test-ar-ef-see')
|
||||
# tricky draft names
|
||||
self.do_rfc_test(draft_name='draft-whatever-02')
|
||||
self.do_rfc_test(draft_name='draft-test-me-03-04')
|
||||
|
||||
def test_rfc_with_tombstone(self):
|
||||
draft = WgDraftFactory(create_revisions=range(0,2))
|
||||
draft.docalias.create(name='rfc3261') # See views_doc.HAS_TOMBSTONE
|
||||
draft.set_state(State.objects.get(type_id='draft',slug='rfc'))
|
||||
draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
|
||||
draft = reload_db_objects(draft)
|
||||
rfc = draft
|
||||
|
||||
# Some old rfcs had tombstones that shouldn't be used for comparisons
|
||||
received = self.getJson(dict(name=rfc.canonical_name()))
|
||||
self.assertTrue(received['previous'].endswith('00'))
|
||||
|
||||
def do_rfc_with_broken_history_test(self, draft_name):
|
||||
draft = WgDraftFactory(rev='10', name=draft_name)
|
||||
draft.docalias.create(name=f'rfc{self.next_rfc_number():04}')
|
||||
draft.set_state(State.objects.get(type_id='draft',slug='rfc'))
|
||||
draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub'))
|
||||
draft = reload_db_objects(draft)
|
||||
rfc = draft
|
||||
|
||||
received = self.getJson(dict(name=draft.name))
|
||||
self.assertEqual(
|
||||
received,
|
||||
dict(
|
||||
content_url=rfc.get_href(),
|
||||
name=rfc.canonical_name(),
|
||||
previous=f'{draft.name}-10',
|
||||
),
|
||||
'RFC by draft name without rev should return canonical RFC name and no rev',
|
||||
)
|
||||
|
||||
received = self.getJson(dict(name=draft.name, rev='10'))
|
||||
self.assertEqual(received['name'], draft.name, 'RFC by draft name with rev should return draft name')
|
||||
self.assertEqual(received['rev'], '10', 'Requested rev should be returned')
|
||||
self.assertEqual(received['previous'], f'{draft.name}-09', 'Previous rev is one less than requested')
|
||||
self.assertIn(f'{draft.name}-10', received['content_url'], 'Returned URL should include requested rev')
|
||||
self.assertNotIn('warning', received, 'No warning when we have the rev requested')
|
||||
|
||||
received = self.getJson(dict(name=f'{draft.name}-09'))
|
||||
self.assertEqual(received['name'], draft.name, 'RFC by draft name with rev should return draft name')
|
||||
self.assertEqual(received['rev'], '09', 'Requested rev should be returned')
|
||||
self.assertEqual(received['previous'], f'{draft.name}-08', 'Previous rev is one less than requested')
|
||||
self.assertIn(f'{draft.name}-09', received['content_url'], 'Returned URL should include requested rev')
|
||||
self.assertEqual(
|
||||
received['warning'],
|
||||
'History for this version not found - these results are speculation',
|
||||
'Warning should be issued when requested rev is not found'
|
||||
)
|
||||
|
||||
def test_rfc_with_broken_history(self):
|
||||
# simple draft name
|
||||
self.do_rfc_with_broken_history_test(draft_name='draft-some-draft')
|
||||
# tricky draft names
|
||||
self.do_rfc_with_broken_history_test(draft_name='draft-gizmo-01')
|
||||
self.do_rfc_with_broken_history_test(draft_name='draft-oh-boy-what-a-draft-02-03')
|
||||
|
||||
|
||||
class RawIdTests(TestCase):
|
||||
|
||||
|
|
|
@ -85,11 +85,6 @@ urlpatterns = [
|
|||
url(r'^html/(?P<name>[Rr][Ff][Cc] [0-9]+?)(\.txt|\.html)?/?$', views_doc.document_html),
|
||||
url(r'^idnits2-rfcs-obsoleted/?$', views_doc.idnits2_rfcs_obsoleted),
|
||||
url(r'^idnits2-rfc-status/?$', views_doc.idnits2_rfc_status),
|
||||
# These two are proof-of-concept of a service that would redirect to the latest version
|
||||
# url(r'^rfcdiff-latest/%(name)s(?:-%(rev)s)?(\.txt|\.html)?/?$' % settings.URL_REGEXPS, views_doc.rfcdiff_latest),
|
||||
# url(r'^rfcdiff-latest/(?P<name>[Rr][Ff][Cc] [0-9]+?)(\.txt|\.html)?/?$', views_doc.rfcdiff_latest),
|
||||
url(r'^rfcdiff-latest-json/%(name)s(?:-%(rev)s)?(\.txt|\.html)?/?$' % settings.URL_REGEXPS, views_doc.rfcdiff_latest_json),
|
||||
url(r'^rfcdiff-latest-json/(?P<name>[Rr][Ff][Cc] [0-9]+?)(\.txt|\.html)?/?$', views_doc.rfcdiff_latest_json),
|
||||
|
||||
url(r'^all/?$', views_search.index_all_drafts),
|
||||
url(r'^active/?$', views_search.index_active_drafts),
|
||||
|
@ -180,4 +175,7 @@ urlpatterns = [
|
|||
url(r'^%(name)s/session/' % settings.URL_REGEXPS, include('ietf.doc.urls_material')),
|
||||
url(r'^(?P<name>[A-Za-z0-9._+-]+)/session/', include(session_patterns)),
|
||||
url(r'^(?P<name>[A-Za-z0-9\._\+\-]+)$', views_search.search_for_name),
|
||||
# latest versions - keep old URLs alive during migration period
|
||||
url(r'^rfcdiff-latest-json/%(name)s(?:-%(rev)s)?(\.txt|\.html)?/?$' % settings.URL_REGEXPS, RedirectView.as_view(pattern_name='ietf.api.views.rfcdiff_latest_json', permanent=True)),
|
||||
url(r'^rfcdiff-latest-json/(?P<name>[Rr][Ff][Cc] [0-9]+?)(\.txt|\.html)?/?$', RedirectView.as_view(pattern_name='ietf.api.views.rfcdiff_latest_json', permanent=True)),
|
||||
]
|
||||
|
|
|
@ -1943,136 +1943,3 @@ def idnits2_state(request, name, rev=None):
|
|||
else:
|
||||
doc.deststatus="Unknown"
|
||||
return render(request, 'doc/idnits2-state.txt', context={'doc':doc}, content_type='text/plain;charset=utf-8')
|
||||
|
||||
def find_doc_for_rfcdiff(name, rev):
|
||||
"""rfcdiff lookup heuristics
|
||||
|
||||
Returns a tuple with:
|
||||
[0] - condition string
|
||||
[1] - document found (or None)
|
||||
[2] - historic version
|
||||
[3] - revision actually found (may differ from :rev: input)
|
||||
"""
|
||||
found = fuzzy_find_documents(name, rev)
|
||||
condition = 'no such document'
|
||||
if found.documents.count() != 1:
|
||||
return (condition, None, None, rev)
|
||||
doc = found.documents.get()
|
||||
if found.matched_rev is None or doc.rev == found.matched_rev:
|
||||
condition = 'current version'
|
||||
return (condition, doc, None, found.matched_rev)
|
||||
else:
|
||||
candidate = doc.history_set.filter(rev=found.matched_rev).order_by("-time").first()
|
||||
if candidate:
|
||||
condition = 'historic version'
|
||||
return (condition, doc, candidate, found.matched_rev)
|
||||
else:
|
||||
condition = 'version dochistory not found'
|
||||
return (condition, doc, None, found.matched_rev)
|
||||
|
||||
# This is a proof of concept of a service that would redirect to the current revision
|
||||
# def rfcdiff_latest(request, name, rev=None):
|
||||
# condition, doc, history = find_doc_for_rfcdiff(name, rev)
|
||||
# if not doc:
|
||||
# raise Http404
|
||||
# if history:
|
||||
# return redirect(history.get_href())
|
||||
# else:
|
||||
# return redirect(doc.get_href())
|
||||
|
||||
HAS_TOMBSTONE = [
|
||||
2821, 2822, 2873, 2919, 2961, 3023, 3029, 3031, 3032, 3033, 3034, 3035, 3036,
|
||||
3037, 3038, 3042, 3044, 3050, 3052, 3054, 3055, 3056, 3057, 3059, 3060, 3061,
|
||||
3062, 3063, 3064, 3067, 3068, 3069, 3070, 3071, 3072, 3073, 3074, 3075, 3076,
|
||||
3077, 3078, 3080, 3081, 3082, 3084, 3085, 3086, 3087, 3088, 3089, 3090, 3094,
|
||||
3095, 3096, 3097, 3098, 3101, 3102, 3103, 3104, 3105, 3106, 3107, 3108, 3109,
|
||||
3110, 3111, 3112, 3113, 3114, 3115, 3116, 3117, 3118, 3119, 3120, 3121, 3123,
|
||||
3124, 3126, 3127, 3128, 3130, 3131, 3132, 3133, 3134, 3135, 3136, 3137, 3138,
|
||||
3139, 3140, 3141, 3142, 3143, 3144, 3145, 3147, 3149, 3150, 3151, 3152, 3153,
|
||||
3154, 3155, 3156, 3157, 3158, 3159, 3160, 3161, 3162, 3163, 3164, 3165, 3166,
|
||||
3167, 3168, 3169, 3170, 3171, 3172, 3173, 3174, 3176, 3179, 3180, 3181, 3182,
|
||||
3183, 3184, 3185, 3186, 3187, 3188, 3189, 3190, 3191, 3192, 3193, 3194, 3197,
|
||||
3198, 3201, 3202, 3203, 3204, 3205, 3206, 3207, 3208, 3209, 3210, 3211, 3212,
|
||||
3213, 3214, 3215, 3216, 3217, 3218, 3220, 3221, 3222, 3224, 3225, 3226, 3227,
|
||||
3228, 3229, 3230, 3231, 3232, 3233, 3234, 3235, 3236, 3237, 3238, 3240, 3241,
|
||||
3242, 3243, 3244, 3245, 3246, 3247, 3248, 3249, 3250, 3253, 3254, 3255, 3256,
|
||||
3257, 3258, 3259, 3260, 3261, 3262, 3263, 3264, 3265, 3266, 3267, 3268, 3269,
|
||||
3270, 3271, 3272, 3273, 3274, 3275, 3276, 3278, 3279, 3280, 3281, 3282, 3283,
|
||||
3284, 3285, 3286, 3287, 3288, 3289, 3290, 3291, 3292, 3293, 3294, 3295, 3296,
|
||||
3297, 3298, 3301, 3302, 3303, 3304, 3305, 3307, 3308, 3309, 3310, 3311, 3312,
|
||||
3313, 3315, 3317, 3318, 3319, 3320, 3321, 3322, 3323, 3324, 3325, 3326, 3327,
|
||||
3329, 3330, 3331, 3332, 3334, 3335, 3336, 3338, 3340, 3341, 3342, 3343, 3346,
|
||||
3348, 3349, 3351, 3352, 3353, 3354, 3355, 3356, 3360, 3361, 3362, 3363, 3364,
|
||||
3366, 3367, 3368, 3369, 3370, 3371, 3372, 3374, 3375, 3377, 3378, 3379, 3383,
|
||||
3384, 3385, 3386, 3387, 3388, 3389, 3390, 3391, 3394, 3395, 3396, 3397, 3398,
|
||||
3401, 3402, 3403, 3404, 3405, 3406, 3407, 3408, 3409, 3410, 3411, 3412, 3413,
|
||||
3414, 3415, 3416, 3417, 3418, 3419, 3420, 3421, 3422, 3423, 3424, 3425, 3426,
|
||||
3427, 3428, 3429, 3430, 3431, 3433, 3434, 3435, 3436, 3437, 3438, 3439, 3440,
|
||||
3441, 3443, 3444, 3445, 3446, 3447, 3448, 3449, 3450, 3451, 3452, 3453, 3454,
|
||||
3455, 3458, 3459, 3460, 3461, 3462, 3463, 3464, 3465, 3466, 3467, 3468, 3469,
|
||||
3470, 3471, 3472, 3473, 3474, 3475, 3476, 3477, 3480, 3481, 3483, 3485, 3488,
|
||||
3494, 3495, 3496, 3497, 3498, 3501, 3502, 3503, 3504, 3505, 3506, 3507, 3508,
|
||||
3509, 3511, 3512, 3515, 3516, 3517, 3518, 3520, 3521, 3522, 3523, 3524, 3525,
|
||||
3527, 3529, 3530, 3532, 3533, 3534, 3536, 3537, 3538, 3539, 3541, 3543, 3544,
|
||||
3545, 3546, 3547, 3548, 3549, 3550, 3551, 3552, 3555, 3556, 3557, 3558, 3559,
|
||||
3560, 3562, 3563, 3564, 3565, 3568, 3569, 3570, 3571, 3572, 3573, 3574, 3575,
|
||||
3576, 3577, 3578, 3579, 3580, 3581, 3582, 3583, 3584, 3588, 3589, 3590, 3591,
|
||||
3592, 3593, 3594, 3595, 3597, 3598, 3601, 3607, 3609, 3610, 3612, 3614, 3615,
|
||||
3616, 3625, 3627, 3630, 3635, 3636, 3637, 3638
|
||||
]
|
||||
|
||||
def rfcdiff_latest_json(request, name, rev=None):
|
||||
response = dict()
|
||||
condition, document, history, found_rev = find_doc_for_rfcdiff(name, rev)
|
||||
|
||||
if condition == 'no such document':
|
||||
raise Http404
|
||||
elif condition in ('historic version', 'current version'):
|
||||
doc = history if history else document
|
||||
if not found_rev and doc.is_rfc():
|
||||
response['content_url'] = doc.get_href()
|
||||
response['name']=doc.canonical_name()
|
||||
if doc.name != doc.canonical_name():
|
||||
prev_rev = doc.rev
|
||||
# not sure what to do if non-numeric values come back, so at least log it
|
||||
log.assertion('doc.rfc_number().isdigit()') # .rfc_number() is expensive...
|
||||
log.assertion('doc.rev.isdigit()')
|
||||
if int(doc.rfc_number()) in HAS_TOMBSTONE and prev_rev != '00':
|
||||
prev_rev = f'{(int(doc.rev)-1):02d}'
|
||||
response['previous'] = f'{doc.name}-{prev_rev}'
|
||||
else:
|
||||
doc.is_rfc = lambda: False
|
||||
response['content_url'] = doc.get_href()
|
||||
response['rev'] = doc.rev
|
||||
response['name'] = doc.name
|
||||
if doc.rev == '00':
|
||||
replaces_docs = (history.doc if condition=='historic version' else doc).related_that_doc('replaces')
|
||||
if replaces_docs:
|
||||
replaces = replaces_docs[0].document
|
||||
response['previous'] = f'{replaces.name}-{replaces.rev}'
|
||||
else:
|
||||
match = re.search("-(rfc)?([0-9][0-9][0-9]+)bis(-.*)?$", name)
|
||||
if match and match.group(2):
|
||||
response['previous'] = f'rfc{match.group(2)}'
|
||||
else:
|
||||
# not sure what to do if non-numeric values come back, so at least log it
|
||||
log.assertion('doc.rev.isdigit()')
|
||||
response['previous'] = f'{doc.name}-{(int(doc.rev)-1):02d}'
|
||||
elif condition == 'version dochistory not found':
|
||||
response['warning'] = 'History for this version not found - these results are speculation'
|
||||
response['name'] = document.name
|
||||
response['rev'] = found_rev
|
||||
document.rev = found_rev
|
||||
document.is_rfc = lambda: False
|
||||
response['content_url'] = document.get_href()
|
||||
# not sure what to do if non-numeric values come back, so at least log it
|
||||
log.assertion('found_rev.isdigit()')
|
||||
if int(found_rev) > 0:
|
||||
response['previous'] = f'{document.name}-{(int(found_rev)-1):02d}'
|
||||
else:
|
||||
match = re.search("-(rfc)?([0-9][0-9][0-9]+)bis(-.*)?$", name)
|
||||
if match and match.group(2):
|
||||
response['previous'] = f'rfc{match.group(2)}'
|
||||
if not response:
|
||||
raise Http404
|
||||
return HttpResponse(json.dumps(response), content_type='application/json')
|
||||
|
|
Loading…
Reference in a new issue