From 9a7908f00dab5ff6c0bd61c623ee50fe49c77899 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Sun, 22 Mar 2026 13:39:53 -0400 Subject: [PATCH 1/5] Add validation test --- tests/test_arr_venue_v2.py | 220 ++++++++++++++++++++++++++++++++++++- 1 file changed, 219 insertions(+), 1 deletion(-) diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 4a4a978b17..cae62864a7 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -27,6 +27,94 @@ arr_ac_max_load_task, arr_sac_max_load_task ) + +ARR_CAMERA_READY_MARKUP_PREPROCESS = r'''async function process(client, edit, invitation) { + client.throwErrors = true; + + const texRules = [ + { name: 'TeX environment', regex: /\\begin\{[^}\n]+\}[\s\S]*?\\end\{[^}\n]+\}/ }, + { name: 'TeX command', regex: /\\[A-Za-z]+(?:\*)?\b/ }, + { name: 'TeX escaped symbol', regex: /\\[%&#_$^{}]/ }, + { name: 'TeX display math', regex: /(^|[^\\])\$\$[\s\S]+?\$\$/ }, + { name: 'TeX inline math', regex: /(^|[^\\])\$(?:[^$\n]|\\\$)+\$/ }, + { name: 'TeX math \\(...\\)', regex: /\\\([\s\S]+?\\\)/ }, + { name: 'TeX math \\[...\\]', regex: /\\\[[\s\S]+?\\\]/ } + ]; + + const markdownRules = [ + { name: 'Markdown link', regex: /\[[^\]\n]+\]\([^)]+\)/ }, + { name: 'Markdown autolink', regex: /\s]+>/i }, + { name: 'Markdown fenced code', regex: /```[\s\S]+?```/ }, + { name: 'Markdown code span', regex: /`[^`\n]+`/ }, + { name: 'Markdown emphasis (*)', regex: /(^|[^\w])\*{1,3}[^*\n]+\*{1,3}(?=$|[^\w])/ }, + { name: 'Markdown emphasis (_)', regex: /(^|[^\w])_{1,2}[^_\n]+_{1,2}(?=$|[^\w])/ } + ]; + + const normalize = (value) => typeof value === 'string' + ? value.replace(/\r\n?/g, '\n').trim() + : ''; + + const findViolation = (text, rules) => { + for (const rule of rules) { + const match = text.match(rule.regex); + if (match) { + return { + rule: rule.name, + snippet: match[0].replace(/\s+/g, ' ').trim().slice(0, 80) + }; + } + } + return null; + }; + + for (const field of ['title', 'abstract']) { + const value = normalize(edit.note?.content?.[field]?.value); + if (!value) { + continue; + } + + const texViolation = findViolation(value, texRules); + if (texViolation) { + return Promise.reject(new OpenReviewError({ + name: 'Error', + message: `${field} cannot contain TeX markup (${texViolation.rule}). Detected: ${texViolation.snippet}` + })); + } + + const markdownViolation = findViolation(value, markdownRules); + if (markdownViolation) { + return Promise.reject(new OpenReviewError({ + name: 'Error', + message: `${field} cannot contain Markdown markup (${markdownViolation.rule}). Detected: ${markdownViolation.snippet}` + })); + } + } + + return Promise.resolve(); +}''' + +ARR_CAMERA_READY_PREPROCESS_LOADER = '''async function process(client, edit, invitation) { + client.throwErrors = true; + const { invitations } = await client.getInvitations({ id: invitation.invitations[0] }); + const metaInvitation = invitations[0]; + const script = metaInvitation.content.revision_preprocess_script.value; + eval(`var process = ${script}`); + await process(client, edit, invitation); +}''' + + +def build_camera_ready_revision_content(submission, invitation_content, title=None, abstract=None): + content = { + 'title': {'value': title if title is not None else submission.content['title']['value']}, + 'abstract': {'value': abstract if abstract is not None else submission.content['abstract']['value']} + } + + for field in ['authors', 'authorids', 'keywords', 'paper_link', 'supplementary_material']: + if field in invitation_content and field in submission.content: + content[field] = {'value': submission.content[field]['value']} + + return content + # API2 template from ICML class TestARRVenueV2(): @@ -6933,8 +7021,138 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers) notes = pc_client_v2.get_notes(forum=submssion3.id, domain='aclweb.org/ACL/ARR/2023/August') assert len(notes) == 5 # submission + review + 3 reports - + + commitment_venue_id = 'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment' + commitment_submissions = openreview_client.get_notes(invitation=f'{commitment_venue_id}/-/Submission', sort='number:asc') + assert commitment_submissions + venue = openreview.helpers.get_conference(client, request_form_note.forum) + venue.submission_revision_stage = openreview.stages.SubmissionRevisionStage( + name='Camera_Ready_Revision', + due_date=due_date, + exp_date=due_date + datetime.timedelta(days=1), + only_accepted=False + ) + venue.create_submission_revision_stage() + helpers.await_queue_edit(openreview_client, f'{commitment_venue_id}/-/Camera_Ready_Revision-0-1', count=1) + + camera_ready_submission = commitment_submissions[0] + camera_ready_invitation_id = f'{commitment_venue_id}/Submission{camera_ready_submission.number}/-/Camera_Ready_Revision' + camera_ready_invitation = None + for _ in range(10): + try: + camera_ready_invitation = openreview_client.get_invitation(camera_ready_invitation_id) + except openreview.OpenReviewException: + camera_ready_invitation = None + if camera_ready_invitation: + break + time.sleep(1) + assert camera_ready_invitation + + openreview_client.post_invitation_edit( + invitations=venue.get_meta_invitation_id(), + readers=[commitment_venue_id], + writers=[commitment_venue_id], + signatures=[commitment_venue_id], + replacement=False, + invitation=openreview.api.Invitation( + id=f'{commitment_venue_id}/-/Camera_Ready_Revision', + content={ + 'revision_preprocess_script': { + 'value': ARR_CAMERA_READY_MARKUP_PREPROCESS + } + }, + edit={ + 'invitation': { + 'preprocess': ARR_CAMERA_READY_PREPROCESS_LOADER + } + } + ) + ) + camera_ready_parent_invitation = None + camera_ready_invitation = None + for _ in range(10): + try: + camera_ready_parent_invitation = openreview_client.get_invitation(f'{commitment_venue_id}/-/Camera_Ready_Revision') + camera_ready_invitation = openreview_client.get_invitation(camera_ready_invitation_id) + except openreview.OpenReviewException: + camera_ready_parent_invitation = None + camera_ready_invitation = None + if camera_ready_parent_invitation and camera_ready_invitation and camera_ready_invitation.preprocess: + break + time.sleep(1) + + assert camera_ready_parent_invitation + assert 'revision_preprocess_script' in camera_ready_parent_invitation.content + assert 'title' in camera_ready_parent_invitation.content['revision_preprocess_script']['value'] + assert 'Markdown link' in camera_ready_parent_invitation.content['revision_preprocess_script']['value'] + + assert camera_ready_invitation + assert camera_ready_invitation.preprocess + assert 'revision_preprocess_script' in camera_ready_invitation.preprocess + assert 'client.getInvitations' in camera_ready_invitation.preprocess + + def post_camera_ready_revision(title=None, abstract=None): + return test_client.post_note_edit( + invitation=camera_ready_invitation_id, + signatures=[f'{commitment_venue_id}/Submission{camera_ready_submission.number}/Authors'], + note=openreview.api.Note( + content=build_camera_ready_revision_content( + camera_ready_submission, + camera_ready_invitation.edit['note']['content'], + title=title, + abstract=abstract + ) + ) + ) + + invalid_cases = [ + ('title', 'Title with inline math $x$', r'cannot contain TeX markup'), + ('title', r'Title with \textit{TeX} command', r'cannot contain TeX markup'), + ('abstract', 'Abstract with $$x + y$$ display math.', r'cannot contain TeX markup'), + ('abstract', r'Abstract with \begin{equation}a=b\end{equation} environment.', r'cannot contain TeX markup'), + ('abstract', r'Abstract with \(x + y\) math delimiters.', r'cannot contain TeX markup'), + ('title', '**Bold** markdown title', r'cannot contain Markdown markup'), + ('title', '_Italic_ markdown title', r'cannot contain Markdown markup'), + ('abstract', 'Abstract with [Appendix](https://example.com) markdown link.', r'cannot contain Markdown markup'), + ('abstract', 'Abstract with `inline code` markers.', r'cannot contain Markdown markup'), + ('abstract', 'Abstract with markdown autolink.', r'cannot contain Markdown markup') + ] + + for field, value, error_match in invalid_cases: + with pytest.raises(openreview.OpenReviewException, match=error_match): + post_camera_ready_revision(**{field: value}) + + valid_cases = [ + { + 'title': '$100K or 100 Days for Better Evaluation', + 'abstract': camera_ready_submission.content['abstract']['value'] + }, + { + 'title': 'Wojood^{Relations} Without Delimited Math', + 'abstract': camera_ready_submission.content['abstract']['value'] + }, + { + 'title': camera_ready_submission.content['title']['value'], + 'abstract': 'We compare [CLS] tokens against plain baselines with ordinary brackets.' + }, + { + 'title': camera_ready_submission.content['title']['value'], + 'abstract': 'A* search and PLAYER* variants remain plain prose here.' + }, + { + 'title': 'Camera-ready title with (parentheses) and [brackets]', + 'abstract': 'A plain URL like https://example.com is allowed because it is not Markdown syntax.' + } + ] + + for case in valid_cases: + revision_edit = post_camera_ready_revision(title=case['title'], abstract=case['abstract']) + helpers.await_queue_edit(openreview_client, edit_id=revision_edit['id']) + updated_submission = openreview_client.get_notes(id=camera_ready_submission.id)[0] + assert updated_submission.content['title']['value'] == case['title'] + assert updated_submission.content['abstract']['value'] == case['abstract'] + venue.invitation_builder.expire_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Senior_Area_Chairs/-/Submission_Group') venue.invitation_builder.expire_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Area_Chairs/-/Submission_Group') venue.invitation_builder.expire_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Reviewers/-/Submission_Group') From 73ae482af1d14c61d5ea4b72df6e72958ccf61b3 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Fri, 27 Mar 2026 08:10:40 -0400 Subject: [PATCH 2/5] Rework tests --- tests/test_arr_venue_v2.py | 73 +++++++++++++------------------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index cae62864a7..59050b67ff 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -93,7 +93,7 @@ return Promise.resolve(); }''' -ARR_CAMERA_READY_PREPROCESS_LOADER = '''async function process(client, edit, invitation) { +ARR_CAMERA_READY_PREPROCESS_CONTENT_SCRIPT = '''async function process(client, edit, invitation) { client.throwErrors = true; const { invitations } = await client.getInvitations({ id: invitation.invitations[0] }); const metaInvitation = invitations[0]; @@ -102,19 +102,6 @@ await process(client, edit, invitation); }''' - -def build_camera_ready_revision_content(submission, invitation_content, title=None, abstract=None): - content = { - 'title': {'value': title if title is not None else submission.content['title']['value']}, - 'abstract': {'value': abstract if abstract is not None else submission.content['abstract']['value']} - } - - for field in ['authors', 'authorids', 'keywords', 'paper_link', 'supplementary_material']: - if field in invitation_content and field in submission.content: - content[field] = {'value': submission.content[field]['value']} - - return content - # API2 template from ICML class TestARRVenueV2(): @@ -7021,10 +7008,7 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers) notes = pc_client_v2.get_notes(forum=submssion3.id, domain='aclweb.org/ACL/ARR/2023/August') assert len(notes) == 5 # submission + review + 3 reports - - commitment_venue_id = 'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment' - commitment_submissions = openreview_client.get_notes(invitation=f'{commitment_venue_id}/-/Submission', sort='number:asc') - assert commitment_submissions + commitment_submissions = openreview_client.get_notes(invitation='aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Submission', sort='number:asc') venue = openreview.helpers.get_conference(client, request_form_note.forum) venue.submission_revision_stage = openreview.stages.SubmissionRevisionStage( @@ -7034,29 +7018,19 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers) only_accepted=False ) venue.create_submission_revision_stage() - helpers.await_queue_edit(openreview_client, f'{commitment_venue_id}/-/Camera_Ready_Revision-0-1', count=1) + helpers.await_queue_edit(openreview_client, f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision-0-1', count=1) camera_ready_submission = commitment_submissions[0] - camera_ready_invitation_id = f'{commitment_venue_id}/Submission{camera_ready_submission.number}/-/Camera_Ready_Revision' - camera_ready_invitation = None - for _ in range(10): - try: - camera_ready_invitation = openreview_client.get_invitation(camera_ready_invitation_id) - except openreview.OpenReviewException: - camera_ready_invitation = None - if camera_ready_invitation: - break - time.sleep(1) - assert camera_ready_invitation + camera_ready_invitation = openreview_client.get_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/-/Camera_Ready_Revision') openreview_client.post_invitation_edit( invitations=venue.get_meta_invitation_id(), - readers=[commitment_venue_id], - writers=[commitment_venue_id], - signatures=[commitment_venue_id], + readers=['aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment'], + writers=['aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment'], + signatures=['aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment'], replacement=False, invitation=openreview.api.Invitation( - id=f'{commitment_venue_id}/-/Camera_Ready_Revision', + id=f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision', content={ 'revision_preprocess_script': { 'value': ARR_CAMERA_READY_MARKUP_PREPROCESS @@ -7064,23 +7038,13 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers) }, edit={ 'invitation': { - 'preprocess': ARR_CAMERA_READY_PREPROCESS_LOADER + 'preprocess': ARR_CAMERA_READY_PREPROCESS_CONTENT_SCRIPT } } ) ) - camera_ready_parent_invitation = None - camera_ready_invitation = None - for _ in range(10): - try: - camera_ready_parent_invitation = openreview_client.get_invitation(f'{commitment_venue_id}/-/Camera_Ready_Revision') - camera_ready_invitation = openreview_client.get_invitation(camera_ready_invitation_id) - except openreview.OpenReviewException: - camera_ready_parent_invitation = None - camera_ready_invitation = None - if camera_ready_parent_invitation and camera_ready_invitation and camera_ready_invitation.preprocess: - break - time.sleep(1) + camera_ready_parent_invitation = openreview_client.get_invitation(f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision') + camera_ready_invitation = openreview_client.get_invitation(f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/-/Camera_Ready_Revision') assert camera_ready_parent_invitation assert 'revision_preprocess_script' in camera_ready_parent_invitation.content @@ -7092,10 +7056,21 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers) assert 'revision_preprocess_script' in camera_ready_invitation.preprocess assert 'client.getInvitations' in camera_ready_invitation.preprocess + def build_camera_ready_revision_content(submission, title=None, abstract=None): + content = { + 'title': {'value': title if title is not None else submission.content['title']['value']}, + 'abstract': {'value': abstract if abstract is not None else submission.content['abstract']['value']} + } + + for field in ['authors', 'authorids', 'keywords', 'paper_link', 'supplementary_material']: + content[field] = {'value': submission.content[field]['value']} + + return content + def post_camera_ready_revision(title=None, abstract=None): return test_client.post_note_edit( - invitation=camera_ready_invitation_id, - signatures=[f'{commitment_venue_id}/Submission{camera_ready_submission.number}/Authors'], + invitation='aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/-/Camera_Ready_Revision', + signatures=['aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/Authors'], note=openreview.api.Note( content=build_camera_ready_revision_content( camera_ready_submission, From 9431a2d89e812c7c0bfb1307f08bfb574f51a4c5 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Fri, 27 Mar 2026 08:12:59 -0400 Subject: [PATCH 3/5] Add await queue edit --- tests/test_arr_venue_v2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 59050b67ff..6e6a63d85d 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -7043,6 +7043,7 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers) } ) ) + helpers.await_queue_edit(openreview_client, f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision-0-1', count=2) camera_ready_parent_invitation = openreview_client.get_invitation(f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision') camera_ready_invitation = openreview_client.get_invitation(f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/-/Camera_Ready_Revision') From b023c98a26a2d61df2b068ee2d5259a0a1cb0945 Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Thu, 9 Apr 2026 15:18:50 +0200 Subject: [PATCH 4/5] Fix tests and move process functions to files --- .../arr/process/camera_ready_preprocess.js | 8 ++ .../camera_ready_preprocess_content.js | 64 ++++++++++++++ tests/test_arr_venue_v2.py | 86 ++----------------- 3 files changed, 81 insertions(+), 77 deletions(-) create mode 100644 openreview/arr/process/camera_ready_preprocess.js create mode 100644 openreview/arr/process/camera_ready_preprocess_content.js diff --git a/openreview/arr/process/camera_ready_preprocess.js b/openreview/arr/process/camera_ready_preprocess.js new file mode 100644 index 0000000000..c547e6f4b2 --- /dev/null +++ b/openreview/arr/process/camera_ready_preprocess.js @@ -0,0 +1,8 @@ +async function process(client, edit, invitation) { + client.throwErrors = true; + const { invitations } = await client.getInvitations({ id: invitation.invitations[0] }); + const metaInvitation = invitations[0]; + const script = metaInvitation.content.revision_preprocess_script.value; + eval(`var process = ${script}`); + await process(client, edit, invitation); +} diff --git a/openreview/arr/process/camera_ready_preprocess_content.js b/openreview/arr/process/camera_ready_preprocess_content.js new file mode 100644 index 0000000000..1dd72a5520 --- /dev/null +++ b/openreview/arr/process/camera_ready_preprocess_content.js @@ -0,0 +1,64 @@ +async function process(client, edit, invitation) { + client.throwErrors = true; + + const texRules = [ + { name: 'TeX environment', regex: /\\begin\{[^}\n]+\}[\s\S]*?\\end\{[^}\n]+\}/ }, + { name: 'TeX command', regex: /\\[A-Za-z]+(?:\*)?\b/ }, + { name: 'TeX escaped symbol', regex: /\\[%&#_$^{}]/ }, + { name: 'TeX display math', regex: /(^|[^\\])\$\$[\s\S]+?\$\$/ }, + { name: 'TeX inline math', regex: /(^|[^\\])\$(?:[^$\n]|\\\$)+\$/ }, + { name: 'TeX math \\(...\\)', regex: /\\\([\s\S]+?\\\)/ }, + { name: 'TeX math \\[...\\]', regex: /\\\[[\s\S]+?\\\]/ } + ]; + + const markdownRules = [ + { name: 'Markdown link', regex: /\[[^\]\n]+\]\([^)]+\)/ }, + { name: 'Markdown autolink', regex: /\s]+>/i }, + { name: 'Markdown fenced code', regex: /```[\s\S]+?```/ }, + { name: 'Markdown code span', regex: /`[^`\n]+`/ }, + { name: 'Markdown emphasis (*)', regex: /(^|[^\w])\*{1,3}[^*\n]+\*{1,3}(?=$|[^\w])/ }, + { name: 'Markdown emphasis (_)', regex: /(^|[^\w])_{1,2}[^_\n]+_{1,2}(?=$|[^\w])/ } + ]; + + const normalize = (value) => typeof value === 'string' + ? value.replace(/\r\n?/g, '\n').trim() + : ''; + + const findViolation = (text, rules) => { + for (const rule of rules) { + const match = text.match(rule.regex); + if (match) { + return { + rule: rule.name, + snippet: match[0].replace(/\s+/g, ' ').trim().slice(0, 80) + }; + } + } + return null; + }; + + for (const field of ['title', 'abstract']) { + const value = normalize(edit.note?.content?.[field]?.value); + if (!value) { + continue; + } + + const texViolation = findViolation(value, texRules); + if (texViolation) { + return Promise.reject(new OpenReviewError({ + name: 'Error', + message: `${field} cannot contain TeX markup (${texViolation.rule}). Detected: ${texViolation.snippet}` + })); + } + + const markdownViolation = findViolation(value, markdownRules); + if (markdownViolation) { + return Promise.reject(new OpenReviewError({ + name: 'Error', + message: `${field} cannot contain Markdown markup (${markdownViolation.rule}). Detected: ${markdownViolation.snippet}` + })); + } + } + + return Promise.resolve(); +} diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index 7546e1d27b..b0231b295e 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -28,79 +28,11 @@ arr_sac_max_load_task ) -ARR_CAMERA_READY_MARKUP_PREPROCESS = r'''async function process(client, edit, invitation) { - client.throwErrors = true; - - const texRules = [ - { name: 'TeX environment', regex: /\\begin\{[^}\n]+\}[\s\S]*?\\end\{[^}\n]+\}/ }, - { name: 'TeX command', regex: /\\[A-Za-z]+(?:\*)?\b/ }, - { name: 'TeX escaped symbol', regex: /\\[%&#_$^{}]/ }, - { name: 'TeX display math', regex: /(^|[^\\])\$\$[\s\S]+?\$\$/ }, - { name: 'TeX inline math', regex: /(^|[^\\])\$(?:[^$\n]|\\\$)+\$/ }, - { name: 'TeX math \\(...\\)', regex: /\\\([\s\S]+?\\\)/ }, - { name: 'TeX math \\[...\\]', regex: /\\\[[\s\S]+?\\\]/ } - ]; - - const markdownRules = [ - { name: 'Markdown link', regex: /\[[^\]\n]+\]\([^)]+\)/ }, - { name: 'Markdown autolink', regex: /\s]+>/i }, - { name: 'Markdown fenced code', regex: /```[\s\S]+?```/ }, - { name: 'Markdown code span', regex: /`[^`\n]+`/ }, - { name: 'Markdown emphasis (*)', regex: /(^|[^\w])\*{1,3}[^*\n]+\*{1,3}(?=$|[^\w])/ }, - { name: 'Markdown emphasis (_)', regex: /(^|[^\w])_{1,2}[^_\n]+_{1,2}(?=$|[^\w])/ } - ]; - - const normalize = (value) => typeof value === 'string' - ? value.replace(/\r\n?/g, '\n').trim() - : ''; - - const findViolation = (text, rules) => { - for (const rule of rules) { - const match = text.match(rule.regex); - if (match) { - return { - rule: rule.name, - snippet: match[0].replace(/\s+/g, ' ').trim().slice(0, 80) - }; - } - } - return null; - }; - - for (const field of ['title', 'abstract']) { - const value = normalize(edit.note?.content?.[field]?.value); - if (!value) { - continue; - } - - const texViolation = findViolation(value, texRules); - if (texViolation) { - return Promise.reject(new OpenReviewError({ - name: 'Error', - message: `${field} cannot contain TeX markup (${texViolation.rule}). Detected: ${texViolation.snippet}` - })); - } - - const markdownViolation = findViolation(value, markdownRules); - if (markdownViolation) { - return Promise.reject(new OpenReviewError({ - name: 'Error', - message: `${field} cannot contain Markdown markup (${markdownViolation.rule}). Detected: ${markdownViolation.snippet}` - })); - } - } - - return Promise.resolve(); -}''' - -ARR_CAMERA_READY_PREPROCESS_CONTENT_SCRIPT = '''async function process(client, edit, invitation) { - client.throwErrors = true; - const { invitations } = await client.getInvitations({ id: invitation.invitations[0] }); - const metaInvitation = invitations[0]; - const script = metaInvitation.content.revision_preprocess_script.value; - eval(`var process = ${script}`); - await process(client, edit, invitation); -}''' +ARR_PROCESS_DIR = os.path.join(os.path.dirname(__file__), '../openreview/arr/process') + +def read_arr_process(process_filename): + with open(os.path.join(ARR_PROCESS_DIR, process_filename)) as process_file: + return process_file.read() # API2 template from ICML class TestARRVenueV2(): @@ -7560,12 +7492,12 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers) id=f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision', content={ 'revision_preprocess_script': { - 'value': ARR_CAMERA_READY_MARKUP_PREPROCESS + 'value': read_arr_process('camera_ready_preprocess_content.js') } }, edit={ 'invitation': { - 'preprocess': ARR_CAMERA_READY_PREPROCESS_CONTENT_SCRIPT + 'preprocess': read_arr_process('camera_ready_preprocess.js') } } ) @@ -7591,7 +7523,8 @@ def build_camera_ready_revision_content(submission, title=None, abstract=None): } for field in ['authors', 'authorids', 'keywords', 'paper_link', 'supplementary_material']: - content[field] = {'value': submission.content[field]['value']} + if field in submission.content: + content[field] = {'value': submission.content[field]['value']} return content @@ -7602,7 +7535,6 @@ def post_camera_ready_revision(title=None, abstract=None): note=openreview.api.Note( content=build_camera_ready_revision_content( camera_ready_submission, - camera_ready_invitation.edit['note']['content'], title=title, abstract=abstract ) From 2b97e169bd8109b3490057187c234e2bf230fb0b Mon Sep 17 00:00:00 2001 From: Harold Rubio Date: Thu, 9 Apr 2026 20:05:17 +0200 Subject: [PATCH 5/5] Add additional test from Copilot comments --- .../arr/process/camera_ready_preprocess_content.js | 13 ++++++++----- tests/test_arr_venue_v2.py | 7 ++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/openreview/arr/process/camera_ready_preprocess_content.js b/openreview/arr/process/camera_ready_preprocess_content.js index 1dd72a5520..266273fa56 100644 --- a/openreview/arr/process/camera_ready_preprocess_content.js +++ b/openreview/arr/process/camera_ready_preprocess_content.js @@ -5,8 +5,8 @@ async function process(client, edit, invitation) { { name: 'TeX environment', regex: /\\begin\{[^}\n]+\}[\s\S]*?\\end\{[^}\n]+\}/ }, { name: 'TeX command', regex: /\\[A-Za-z]+(?:\*)?\b/ }, { name: 'TeX escaped symbol', regex: /\\[%&#_$^{}]/ }, - { name: 'TeX display math', regex: /(^|[^\\])\$\$[\s\S]+?\$\$/ }, - { name: 'TeX inline math', regex: /(^|[^\\])\$(?:[^$\n]|\\\$)+\$/ }, + { name: 'TeX display math', regex: /(^|[^\\])\$\$[\s\S]+?\$\$/, trimPrefixCapture: true }, + { name: 'TeX inline math', regex: /(^|[^\\])\$(?:[^$\n]|\\\$)+\$/, trimPrefixCapture: true }, { name: 'TeX math \\(...\\)', regex: /\\\([\s\S]+?\\\)/ }, { name: 'TeX math \\[...\\]', regex: /\\\[[\s\S]+?\\\]/ } ]; @@ -16,8 +16,8 @@ async function process(client, edit, invitation) { { name: 'Markdown autolink', regex: /\s]+>/i }, { name: 'Markdown fenced code', regex: /```[\s\S]+?```/ }, { name: 'Markdown code span', regex: /`[^`\n]+`/ }, - { name: 'Markdown emphasis (*)', regex: /(^|[^\w])\*{1,3}[^*\n]+\*{1,3}(?=$|[^\w])/ }, - { name: 'Markdown emphasis (_)', regex: /(^|[^\w])_{1,2}[^_\n]+_{1,2}(?=$|[^\w])/ } + { name: 'Markdown emphasis (*)', regex: /(^|[^\w])\*{1,3}[^*\n]+\*{1,3}(?=$|[^\w])/, trimPrefixCapture: true }, + { name: 'Markdown emphasis (_)', regex: /(^|[^\w])_{1,2}[^_\n]+_{1,2}(?=$|[^\w])/, trimPrefixCapture: true } ]; const normalize = (value) => typeof value === 'string' @@ -28,9 +28,12 @@ async function process(client, edit, invitation) { for (const rule of rules) { const match = text.match(rule.regex); if (match) { + const snippet = rule.trimPrefixCapture + ? match[0].slice((match[1] || '').length) + : match[0]; return { rule: rule.name, - snippet: match[0].replace(/\s+/g, ' ').trim().slice(0, 80) + snippet: snippet.replace(/\s+/g, ' ').trim().slice(0, 80) }; } } diff --git a/tests/test_arr_venue_v2.py b/tests/test_arr_venue_v2.py index b0231b295e..2a0ce576d0 100644 --- a/tests/test_arr_venue_v2.py +++ b/tests/test_arr_venue_v2.py @@ -7480,7 +7480,6 @@ def test_commitment_venue(self, client, test_client, openreview_client, helpers) helpers.await_queue_edit(openreview_client, f'aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/-/Camera_Ready_Revision-0-1', count=1) camera_ready_submission = commitment_submissions[0] - camera_ready_invitation = openreview_client.get_invitation('aclweb.org/ACL/2024/Workshop/C3NLP_ARR_Commitment/Submission1/-/Camera_Ready_Revision') openreview_client.post_invitation_edit( invitations=venue.get_meta_invitation_id(), @@ -7558,6 +7557,12 @@ def post_camera_ready_revision(title=None, abstract=None): with pytest.raises(openreview.OpenReviewException, match=error_match): post_camera_ready_revision(**{field: value}) + with pytest.raises(openreview.OpenReviewException, match=r'Detected: \$x\$'): + post_camera_ready_revision(title='Camera ready titleo$x$') + + with pytest.raises(openreview.OpenReviewException, match=r'Detected: \$\$x\+y\$\$'): + post_camera_ready_revision(abstract='Camera ready abstracto$$x+y$$') + valid_cases = [ { 'title': '$100K or 100 Days for Better Evaluation',