Initial version

This commit is contained in:
Tom Kasper
2026-08-30 20:44:00 +02:00
commit 9aa2da72b5
612 changed files with 64514 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
"""templates.py — template search/ranking and creation flow (plan §9, §13)."""
import pytest
from fakes import FakeElabftwClient, FakeTemplate, make_template
from mcp_server.errors import NotFoundError
def ranking_client() -> FakeElabftwClient:
return FakeElabftwClient(
templates=[
FakeTemplate(id=1, title="Cell culture", description="Maintain adherent lines."),
FakeTemplate(
id=2,
title="Protein extraction",
description="Lyse cells; collect supernatant in pcr tubes.",
),
FakeTemplate(id=3, title="Ethanol precipitation", description="Precipitate DNA.", tags=["pcr"]),
FakeTemplate(id=4, title="PCR cleanup", description="Purify an amplicon."),
]
)
# --- short description ------------------------------------------------------
async def test_short_description_is_first_paragraph(templates_service, client):
client.templates[7] = make_template()
result = await templates_service.list_templates()
assert result[0].short_description == "Purify an amplicon after PCR."
async def test_short_description_truncated_to_limit(templates_service, settings, client):
settings.labvoice_template_desc_limit = 20
client.templates[7] = make_template(
description="P" * 100 + "\n\nSecond paragraph that must not appear."
)
result = await templates_service.list_templates()
summary = result[0]
assert len(summary.short_description) <= 20
assert "Second paragraph" not in summary.short_description
async def test_short_description_empty_when_no_description(templates_service, client):
client.templates[7] = make_template(description="")
result = await templates_service.list_templates()
assert result[0].short_description == ""
async def test_summary_carries_id_title_tags(templates_service, client):
client.templates[7] = make_template()
result = await templates_service.list_templates()
summary = result[0]
assert summary.id == 7
assert summary.title == "PCR cleanup"
assert summary.tags == ["dna", "cleanup"]
# --- ranking ----------------------------------------------------------------
async def test_no_query_preserves_listing_order(templates_service, client):
client.templates.update({t.id: t for t in ranking_client().templates.values()})
result = await templates_service.list_templates()
assert [t.id for t in result] == [1, 2, 3, 4]
async def test_title_match_ranks_above_tag_and_description_match(templates_service, client):
client.templates.update({t.id: t for t in ranking_client().templates.values()})
result = await templates_service.list_templates(query="pcr")
ids = [t.id for t in result]
assert ids.index(4) < ids.index(3) < ids.index(2), (
"expected title(4) > tags(3) > description(2) per plan §9 weights"
)
async def test_non_matching_templates_rank_below_matches(templates_service, client):
client.templates.update({t.id: t for t in ranking_client().templates.values()})
result = await templates_service.list_templates(query="pcr")
assert result[-1].id == 1
async def test_limit_is_respected(templates_service, client):
client.templates.update({t.id: t for t in ranking_client().templates.values()})
result = await templates_service.list_templates(query="pcr", limit=2)
assert len(result) == 2
assert result[0].id == 4
# --- creation ----------------------------------------------------------------
async def test_create_from_template_returns_experiment_and_first_step(templates_service, client):
client.templates[7] = make_template()
result = await templates_service.create_experiment_from_template(7)
assert result.ok is True
assert result.experiment_id == 1000
assert result.title == "PCR cleanup"
assert result.first_step is not None
assert result.first_step.id == 41
assert result.first_step.finished is False
async def test_create_with_explicit_title(templates_service, client):
client.templates[7] = make_template()
result = await templates_service.create_experiment_from_template(7, title="My amplicon run")
assert result.title == "My amplicon run"
async def test_created_experiment_copies_template_steps_and_annotations(
templates_service, client
):
"""eLabFTW copies template steps — annotations must come along (plan §9)."""
from fakes import ANNOTATION_ETHANOL_FIFO, FakeStep
client.templates[7] = make_template(
steps=[FakeStep(id=41, body=f"Add ethanol\n{ANNOTATION_ETHANOL_FIFO}", ordering=1)]
)
result = await templates_service.create_experiment_from_template(7)
created = client.experiments[result.experiment_id]
assert "labvoice:v1" in created.steps[0].body
async def test_create_from_unknown_template_fails_cleanly(templates_service, client):
with pytest.raises((NotFoundError, KeyError)):
await templates_service.create_experiment_from_template(9999)
async def test_search_index_build_is_optional(templates_service, client):
"""Lexical fallback works without an explicitly built index (plan §9 fallback)."""
client.templates.update({t.id: t for t in ranking_client().templates.values()})
result = await templates_service.list_templates(query="pcr")
assert result[0].id == 4