mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-31 10:12:21 +01:00
fix(notes): honor explicit null when clearing optional fields
This commit is contained in:
@@ -728,32 +728,35 @@ def setup_note_routes(task_scheduler=None, upload_handler=None):
|
||||
)
|
||||
if body.title is not None:
|
||||
note.title = body.title
|
||||
if body.content is not None:
|
||||
# Omitted fields preserve their values; explicit null clears the
|
||||
# nullable fields used by the editor's removal controls.
|
||||
supplied = body.model_fields_set
|
||||
if "content" in supplied:
|
||||
note.content = body.content
|
||||
if body.items is not None:
|
||||
note.items = json.dumps(body.items)
|
||||
if "items" in supplied:
|
||||
note.items = json.dumps(body.items) if body.items is not None else None
|
||||
flag_modified(note, "items")
|
||||
if body.note_type is not None:
|
||||
note.note_type = body.note_type
|
||||
if body.color is not None:
|
||||
if "color" in supplied:
|
||||
note.color = body.color
|
||||
if body.label is not None:
|
||||
if "label" in supplied:
|
||||
note.label = body.label
|
||||
if body.pinned is not None:
|
||||
note.pinned = body.pinned
|
||||
if body.archived is not None:
|
||||
note.archived = body.archived
|
||||
if body.due_date is not None:
|
||||
if "due_date" in supplied:
|
||||
note.due_date = body.due_date
|
||||
if body.image_url is not None:
|
||||
if "image_url" in supplied:
|
||||
note.image_url = body.image_url
|
||||
if body.gallery_id is not None:
|
||||
if "gallery_id" in supplied:
|
||||
note.gallery_id = body.gallery_id
|
||||
if body.repeat is not None:
|
||||
note.repeat = body.repeat
|
||||
if body.sort_order is not None:
|
||||
note.sort_order = body.sort_order
|
||||
if body.agent_session_id is not None:
|
||||
if "agent_session_id" in supplied:
|
||||
note.agent_session_id = body.agent_session_id
|
||||
|
||||
db.commit()
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Explicit null must clear nullable note fields; omission must preserve them."""
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from tests.helpers.database import disposable_database
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(tmp_path, monkeypatch):
|
||||
import routes.note.note_routes as routes
|
||||
|
||||
with disposable_database(tmp_path) as factory:
|
||||
monkeypatch.setattr(routes, "SessionLocal", factory)
|
||||
app = FastAPI()
|
||||
|
||||
@app.middleware("http")
|
||||
async def identity(request, call_next):
|
||||
request.state.current_user = request.headers.get("x-test-user", "alice")
|
||||
return await call_next(request)
|
||||
|
||||
monkeypatch.setattr(routes, "_scheduler_ref", None)
|
||||
app.include_router(routes.setup_note_routes())
|
||||
with TestClient(app) as api:
|
||||
yield api
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field,value", [
|
||||
("due_date", "2030-10-08T09:00:00"),
|
||||
("label", "home"),
|
||||
("image_url", "https://example.com/note.png"),
|
||||
("content", "Saved body"),
|
||||
("items", [{"text": "Saved item", "done": False}]),
|
||||
("color", "blue"),
|
||||
("gallery_id", "gallery-1"),
|
||||
("agent_session_id", "chat-1"),
|
||||
])
|
||||
def test_explicit_null_clears_saved_field(client, field, value):
|
||||
created = client.post("/api/notes", json={"title": "Test note"})
|
||||
assert created.status_code == 200, created.text
|
||||
path = f"/api/notes/{created.json()['id']}"
|
||||
populated = client.put(path, json={field: value})
|
||||
assert populated.status_code == 200, populated.text
|
||||
assert client.get(path).json()[field] == value
|
||||
|
||||
updated = client.put(path, json={field: None})
|
||||
assert updated.status_code == 200, updated.text
|
||||
saved = client.get(path)
|
||||
assert saved.status_code == 200, saved.text
|
||||
assert saved.json()[field] is None
|
||||
assert updated.json()[field] is None
|
||||
|
||||
|
||||
def test_omitted_fields_survive_title_edit(client):
|
||||
fields = {
|
||||
"due_date": "2030-10-08T09:00:00",
|
||||
"label": "home",
|
||||
"image_url": "https://example.com/note.png",
|
||||
"content": "Saved body",
|
||||
"items": [{"text": "Saved item", "done": False}],
|
||||
"color": "blue",
|
||||
"gallery_id": "gallery-1",
|
||||
}
|
||||
created = client.post("/api/notes", json={"title": "Before", **fields})
|
||||
assert created.status_code == 200, created.text
|
||||
path = f"/api/notes/{created.json()['id']}"
|
||||
updated = client.put(path, json={"title": "After"})
|
||||
assert updated.status_code == 200, updated.text
|
||||
saved = client.get(path).json()
|
||||
assert saved["title"] == "After"
|
||||
for field, value in fields.items():
|
||||
assert saved[field] == value
|
||||
|
||||
|
||||
def test_null_update_cannot_clear_another_users_note(client):
|
||||
created = client.post("/api/notes", json={"title": "Private", "label": "home"})
|
||||
assert created.status_code == 200, created.text
|
||||
path = f"/api/notes/{created.json()['id']}"
|
||||
response = client.put(path, json={"label": None}, headers={"x-test-user": "bob"})
|
||||
assert response.status_code == 404
|
||||
assert client.get(path).json()["label"] == "home"
|
||||
Reference in New Issue
Block a user