"""An explicitly requested reply-to-reply branch needs new, linked evidence.""" from copy import deepcopy import hashlib import pytest from backend.prospecting.discussion_scope import conditions, requirements NOTE = 'a' * 24 URL = 'https://www.xiaohongshu.com/explore/' + NOTE GOAL = '先保存目前已显示的父回复,再展开同组回复,保存一条明确回复该父回复的新增子回复。' def task(goal=GOAL, **inputs): return {'inputs':{'goal':goal,'intent':'inspect','subject_urls':[URL],'target_candidates':None, '_goal_version':1,'max_comments_per_note':5,**inputs}, 'artifacts':{'messages':[{'role':'user','text':goal}],'sources':[],'operation_evidence':[]}} def source(row, sid, kind, parent=None, root=None, note_id=NOTE): item = {'source_id':sid,'source_type':kind,'note_id':note_id,'source_url':URL, 'source_selector':'#'+sid,'content':'原话'+sid,'parent_source_id':parent,'root_source_id':root} row['artifacts']['sources'].append(item) if kind != 'note': row['artifacts']['operation_evidence'].append({ 'capability':'save_source','source_kind':kind,'outcome':'success', 'verifier':'source_container_quote_and_persisted','evidence_refs':{'pre':'before','post':'after'}, 'details':{'trace':{'goal_version':1},'pre_snapshot':{'note_id':note_id,'container_selector':'#'+sid,'quote_matched':True}, 'post_snapshot':{'source_id':sid,'source_type':kind,'persisted':True,'relations_checked':True, 'parent_source_id':parent,'root_source_id':root}}}) return item def expansion(row, reply): fact = {'capability':'expand_replies','source_kind':'reply','outcome':'success','verifier':'reply_delta_and_parent', 'evidence_refs':{'pre':'before-expand','post':'after-expand'}, 'details':{'trace':{'goal_version':1},'pre_snapshot':{'note_id':NOTE}, 'post_snapshot':{'note_id':NOTE,'parent_scope_checked':True,'status':'expanded','owner_reply_delta':1, 'new_visible_replies':1,'new_reply_fingerprints':[ hashlib.sha256((reply['source_selector']+reply['content']).encode()).hexdigest()]}}} row['artifacts']['operation_evidence'].append(fact) return fact def ready(*, nested=True): row = task() source(row,'note','note') source(row,'root','comment') source(row,'parent','reply',parent='root',root='root') child = source(row,'child','reply',parent='parent' if nested else 'root',root='root') expansion(row,child) return row def depth(row): return next(c for c in conditions(row) if c['key']=='discussion_reply_to_reply') @pytest.mark.parametrize('goal', [GOAL,'保存一条回复另一条回复的新增子回复。','保存回复父回复的子回复。']) def test_explicit_depth_wording_requires_this_specific_branch(goal): row = task(goal) assert 'reply_to_reply' in requirements(row) assert depth(row)['required'] and not depth(row)['met'] @pytest.mark.parametrize('goal', ['核对完整回复链。','核对发言人、直接父评论或父回复、根讨论。','保存父评论和子回复。']) def test_generic_complete_chain_or_optional_parent_kind_does_not_add_depth(goal): assert 'reply_to_reply' not in requirements(task(goal)) def test_root_to_direct_reply_does_not_fulfill_reply_to_reply_branch(): row = ready(nested=False) assert next(c for c in conditions(row) if c['key']=='discussion_expansion')['met'] assert not depth(row)['met'] def test_actual_new_child_and_observed_saved_parent_prove_depth_without_mutation(): row = ready() before = deepcopy(row) assert depth(row)['met'] and depth(row)['current']==depth(row)['target']==1 assert row==before and row['inputs']['max_comments_per_note']==5 @pytest.mark.parametrize('change', [ lambda row: row['artifacts']['operation_evidence'][-1]['details']['trace'].update(goal_version=0), lambda row: row['artifacts']['operation_evidence'][-1]['details']['post_snapshot'].update(new_reply_fingerprints=['not-this-child']), lambda row: row['artifacts']['operation_evidence'][2]['details']['trace'].update(goal_version=0), lambda row: row['artifacts']['operation_evidence'][1]['details']['trace'].update(goal_version=0), lambda row: row['artifacts']['operation_evidence'][2]['details']['post_snapshot'].update(relations_checked=False), lambda row: row['artifacts']['operation_evidence'][1].update(outcome='unknown'), lambda row: row['artifacts']['operation_evidence'][2]['details']['post_snapshot'].update(parent_source_id='root'), lambda row: row['artifacts']['sources'][-2].update(parent_source_id='child'), lambda row: row['artifacts']['sources'][-2].update(note_id='other-note'), lambda row: row['artifacts']['operation_evidence'].pop(), ]) def test_old_unrelated_weak_or_cyclic_proof_cannot_establish_requested_depth(change): row = ready() change(row) assert not depth(row)['met'] def test_expanding_a_different_direct_reply_does_not_prove_already_visible_nested_reply_is_new(): row = ready() old = source(row,'new-direct','reply',parent='root',root='root') row['artifacts']['operation_evidence'] = [f for f in row['artifacts']['operation_evidence'] if f['capability']!='expand_replies'] expansion(row,old) assert not depth(row)['met'] @pytest.mark.parametrize('goal', ['不要保存回复该父回复的新增子回复。','只保存正文。比如回复另一条回复。', '只保存正文。\n> 保存回复父回复的子回复。','只保存正文。请忽略引用“回复该父回复”。']) def test_negated_or_quoted_depth_does_not_expand_user_scope(goal): assert 'reply_to_reply' not in requirements(task(goal)) def test_later_cancellation_removes_only_explicit_depth(): row = ready() row['artifacts']['messages'].append({'role':'user','text':'不用保存回复该父回复的新增子回复。'}) assert 'reply_to_reply' not in requirements(row) assert 'expansion' in requirements(row) def test_depth_requires_each_saved_note_in_current_requested_scope(): row = ready() other_note='b'*24 row['inputs']['subject_urls'].append('https://www.xiaohongshu.com/explore/'+other_note) source(row,'second-note','note',note_id=other_note) assert not depth(row)['met'] assert (depth(row)['current'],depth(row)['target'])==(1,2)