Build and Push Docker Container / build-and-push (push) Successful in 3m31s
- Serialize property set and remove commands per vault note to avoid lost frontmatter writes. - Wait briefly for successful property writes to hit disk before releasing the note lock. - Return property path, name, value, type, operation, and ok status in n8n outputs. - Include property details on continue-on-fail errors so failed items are visible in n8n. - Bump obsidian-api to 1.0.7 and n8n-nodes-obsidian-cli-api to 0.2.8. - Add regression coverage for concurrent property writes to the same note.
88 lines
3.6 KiB
Python
88 lines
3.6 KiB
Python
import asyncio
|
|
|
|
from conftest import client, use_jwt_auth, use_work_vault
|
|
|
|
def test_property_set_name_is_not_treated_as_path(monkeypatch, tmp_path):
|
|
vault = tmp_path / "vault"
|
|
vault.mkdir()
|
|
use_work_vault(monkeypatch, vault)
|
|
token = use_jwt_auth(monkeypatch, {"sub": "journal", "commands": ["property:set"], "vaults": ["work"], "paths": ["00. Journal/"]})
|
|
|
|
response = client.post(
|
|
"/commands",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
json={"args": ["vault=work", "property:set", "path=00. Journal/Note.md", "name=from", "value=Daniel", "type=text"], "cwd": str(vault)},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["command"] == ["python", "vault=work", "property:set", "path=00. Journal/Note.md", "name=from", "value=Daniel", "type=text"]
|
|
|
|
def test_property_set_creates_missing_file(monkeypatch, tmp_path):
|
|
vault = tmp_path / "vault"
|
|
vault.mkdir()
|
|
use_work_vault(monkeypatch, vault)
|
|
token = use_jwt_auth(monkeypatch, {"sub": "journal", "commands": ["property:set"], "vaults": ["work"], "paths": ["00. Journal/"]})
|
|
|
|
response = client.post(
|
|
"/commands",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
json={"args": ["vault=work", "property:set", "path=00. Journal/New.md", "name=from", "value=Daniel", "type=text"], "cwd": str(vault)},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert (vault / "00. Journal" / "New.md").exists()
|
|
|
|
def test_property_set_datetime_defaults_missing_time(monkeypatch, tmp_path):
|
|
vault = tmp_path / "vault"
|
|
vault.mkdir()
|
|
use_work_vault(monkeypatch, vault)
|
|
token = use_jwt_auth(monkeypatch, {"sub": "journal", "commands": ["property:set"], "vaults": ["work"], "paths": ["00. Journal/"]})
|
|
|
|
response = client.post(
|
|
"/commands",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
json={"args": ["vault=work", "property:set", "path=00. Journal/New.md", "name=from", "value=2026-06-24T", "type=datetime"], "cwd": str(vault)},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["command"] == ["python", "vault=work", "property:set", "path=00. Journal/New.md", "name=from", "value=2026-06-24T00:00:00", "type=datetime"]
|
|
|
|
def test_property_writes_to_same_note_are_serialized(monkeypatch, tmp_path):
|
|
import app.runner as runner
|
|
|
|
vault = tmp_path / "vault"
|
|
vault.mkdir()
|
|
calls:list[str] = []
|
|
|
|
async def fake_run_process(command, cwd, stdin, timeout):
|
|
calls.append(command[command.index("name=first") if "name=first" in command else command.index("name=second")])
|
|
if "name=first" in command:
|
|
await asyncio.sleep(0.01)
|
|
assert calls == ["name=first"] or calls == ["name=first", "name=second"]
|
|
(vault / "Daily" / "Note.md").write_text("\n".join(calls), encoding="utf-8")
|
|
return b"", b"", 0, False
|
|
|
|
monkeypatch.setattr(runner, "run_process", fake_run_process)
|
|
|
|
async def run_commands():
|
|
await asyncio.gather(
|
|
runner.execute_command(["vault=work", "property:set", "path=Daily/Note.md", "name=first", "value=1", "type=text"], vault, None, 60),
|
|
runner.execute_command(["vault=work", "property:set", "path=Daily/Note.md", "name=second", "value=2", "type=text"], vault, None, 60),
|
|
)
|
|
|
|
asyncio.run(run_commands())
|
|
|
|
assert calls == ["name=first", "name=second"]
|
|
|
|
def test_rename_name_is_treated_as_path(monkeypatch):
|
|
token = use_jwt_auth(monkeypatch, {"sub": "journal", "commands": ["rename"], "vaults": ["*"], "paths": ["00. Journal/"]})
|
|
|
|
response = client.post(
|
|
"/commands",
|
|
headers={"Authorization": f"Bearer {token}"},
|
|
json={"args": ["rename", "path=00. Journal/Old.md", "name=Private/New.md"]},
|
|
)
|
|
|
|
assert response.status_code == 403
|
|
assert response.json()["detail"].startswith("token is not allowed to access path: Private/New.md")
|