Install and update artemis-use with task diagnostics and plugin guidance. Preserve cross-device file receipts, review canonical reports, guard stage transitions and reduce small history summaries. Improve exploration lifecycle, request recovery and browser live connections.
189 lines
6.7 KiB
Python
189 lines
6.7 KiB
Python
# 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("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"
|