# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from artemis.utils.notes import ( append_note_content, list_notes_info, read_note_content, save_note_content, save_verified_note, update_note_content, ) import pytest @pytest.mark.parametrize("operation", ["save", "append", "update"]) @pytest.mark.parametrize("path_style", ["native", "posix"]) def test_note_rejects_misplaced_artifact_and_accepts_exact_receipt(tmp_path, operation, path_style): artifact = tmp_path / "artifacts" / "android" / "截图 one.png" artifact.parent.mkdir(parents=True) artifact.write_bytes(b"evidence") save_note_content(tmp_path, "evidence", "original") note = tmp_path / "notes" / "evidence.md" before = note.read_bytes() missing = tmp_path / artifact.name wrong = str(missing) if path_style == "native" else missing.as_posix() def write(content): if operation == "save": save_note_content(tmp_path, "evidence", content) elif operation == "append": append_note_content(tmp_path, "evidence", content) else: update_note_content(tmp_path, "evidence", "original", content) with pytest.raises(ValueError, match="exact trace_path") as error: write(f"Screenshot: `{wrong}`。") assert str(artifact) in str(error.value) assert note.read_bytes() == before assert artifact.read_bytes() == b"evidence" write(f"Screenshot: `{artifact}`") assert str(artifact) in note.read_text(encoding="utf-8") def test_note_does_not_guess_between_duplicate_artifact_names(tmp_path): files = [ tmp_path / "artifacts" / "android" / "screen.png", tmp_path / "artifacts" / "plugins" / "cua-driver" / "screen.png", ] for path in files: path.parent.mkdir(parents=True) path.write_bytes(b"evidence") with pytest.raises(ValueError) as error: save_note_content(tmp_path, "evidence", str(tmp_path / "screen.png")) assert all(str(path) in str(error.value) for path in files) assert not (tmp_path / "notes" / "evidence.md").exists() def test_note_allows_existing_root_files_and_unknown_future_paths(tmp_path): artifact = tmp_path / "artifacts" / "android" / "screen.png" artifact.parent.mkdir(parents=True) artifact.write_bytes(b"artifact") root_file = tmp_path / "screen.png" root_file.write_bytes(b"different root file") content = f"{root_file}\n{tmp_path / 'future.png'}\n{tmp_path / 'screen.png.backup'}" save_note_content(tmp_path, "plan", content) assert read_note_content(tmp_path, "plan") == content @pytest.mark.parametrize("content", ["# 报告\n结果\n", "line\r\nsecond", ""]) def test_verified_note_receipt_matches_actual_file_bytes(tmp_path, content): import hashlib receipt = save_verified_note(tmp_path, "report.md", content) data = (tmp_path / receipt["path"]).read_bytes() assert receipt["path"] == "notes/report.md" assert receipt["verified"] is True and receipt["bytes"] == len(data) assert receipt["sha256"] == hashlib.sha256(data).hexdigest() def test_verified_note_does_not_confirm_incomplete_write(tmp_path, monkeypatch): from artemis.utils import notes def partial_write(base_dir, key, content): save_note_content(base_dir, key, content[:1]) monkeypatch.setattr(notes, "save_note_content", partial_write) with pytest.raises(OSError, match="读回不一致"): save_verified_note(tmp_path, "report", "完整报告") def test_list_notes_info(tmp_path): base_dir = tmp_path # Create some notes save_note_content(base_dir, "note1", "line1\nline2\nline3") save_note_content(base_dir, "note2", "line1\nline2") info = list_notes_info(base_dir) assert info == {"note1": 3, "note2": 2} def test_read_note_content_range(tmp_path): base_dir = tmp_path save_note_content(base_dir, "note1", "line1\nline2\nline3\nline4") # Read full assert read_note_content(base_dir, "note1") == "line1\nline2\nline3\nline4" # Read range assert read_note_content(base_dir, "note1", start_line=2, end_line=3) == "line2\nline3" # Read from start to line assert read_note_content(base_dir, "note1", end_line=2) == "line1\nline2" # Read from line to end assert read_note_content(base_dir, "note1", start_line=3) == "line3\nline4" def test_update_note_exact_match(tmp_path): base_dir = tmp_path original_content = 'def demo():\n print("Hello, World!")\n return True\n' save_note_content(base_dir, "task_plan", original_content) # Exact match warning = update_note_content( base_dir, "task_plan", ' print("Hello, World!")', ' print("Hello, Jetski!")', ) assert warning is None updated_content = read_note_content(base_dir, "task_plan") assert 'print("Hello, Jetski!")' in updated_content assert 'print("Hello, World!")' not in updated_content def test_update_note_relaxed_match(tmp_path): base_dir = tmp_path # Multi-line content with checkboxes and indentation original_content = ( "- [ ] Step 1: Research the issue\n - [ ] Step 2: Write code\n - [ ] Step 3: Run tests\n" ) save_note_content(base_dir, "task_plan", original_content) # Relaxed match: case difference, quote difference, checkbox bullet difference, trailing whitespace target = " * [ ] step 2: write code " replacement = " - [/] Step 2: In progress" warning = update_note_content(base_dir, "task_plan", target, replacement) # Warning should be returned assert warning is not None assert "We did our best to apply changes despite some inaccuracies" in warning updated_content = read_note_content(base_dir, "task_plan") assert "Step 2: In progress" in updated_content assert "Step 2: Write code" not in updated_content # Check indentation is preserved from replacement assert " - [/] Step 2: In progress\n" in updated_content def test_update_note_fuzzy_match(tmp_path): base_dir = tmp_path original_content = 'print("Alpha message")\nprint("Gamma message")\nprint("Delta message")\n' save_note_content(base_dir, "task_plan", original_content) # Fuzzy match: typo (Gemma vs Gamma) target = 'print("Gemma message")' replacement = 'print("Gamma updated!")' warning = update_note_content(base_dir, "task_plan", target, replacement) assert warning is not None assert "We did our best to apply changes despite some inaccuracies" in warning updated_content = read_note_content(base_dir, "task_plan") assert 'print("Gamma updated!")' in updated_content assert 'print("Gamma message")' not in updated_content def test_update_note_ambiguous_match(tmp_path): base_dir = tmp_path # Multiple lines that could match target original_content = 'print("Beta message")\nprint("Zeta message")\nprint("Eta message")\n' save_note_content(base_dir, "task_plan", original_content) # Target is highly ambiguous (Xeta is almost equally close to Beta and Zeta) target = 'print("Xeta message")' replacement = 'print("Ambiguous match!")' # Should raise ValueError due to ambiguity or inability to find unique match with pytest.raises(ValueError) as exc_info: update_note_content(base_dir, "task_plan", target, replacement) assert "Target string 'print(\"Xeta message\")' not found" in str( exc_info.value ) or "not unique" in str(exc_info.value) def test_update_note_not_found(tmp_path): base_dir = tmp_path original_content = 'print("Alpha message")\n' save_note_content(base_dir, "task_plan", original_content) # Totally different target target = 'print("Something totally different")' replacement = 'print("Hello")' with pytest.raises(ValueError) as exc_info: update_note_content(base_dir, "task_plan", target, replacement) assert "not found" in str(exc_info.value) def test_append_note_content(tmp_path): base_dir = tmp_path # Case 1: File does not exist append_note_content(base_dir, "new_note", "hello") assert read_note_content(base_dir, "new_note") == "hello\n" # Case 2: File exists and ends with a newline append_note_content(base_dir, "new_note", "world") assert read_note_content(base_dir, "new_note") == "hello\nworld\n" # Case 3: File exists and does not end with a newline file_path = base_dir / "notes" / "no_newline.md" file_path.parent.mkdir(parents=True, exist_ok=True) file_path.write_text("no newline", encoding="utf-8") append_note_content(base_dir, "no_newline", "added text") assert read_note_content(base_dir, "no_newline") == "no newline\nadded text\n"