from lxml import html
from lxml.html import HtmlElement
from timApp.auth.accesstype import AccessType
from timApp.document.docparagraph import DocParagraph
from timApp.document.viewcontext import default_view_ctx
from timApp.plugin.plugin import Plugin
from timApp.tests.server.timroutetest import TimRouteTest
from timApp.timdb.sqa import db
[docs]class EditTest(TimRouteTest):
[docs] def test_nonexistent_edit(self):
self.login_test1()
d = self.create_doc(initial_par="test")
par_id = d.document.get_paragraphs()[0].get_id()
invalid_par = "nonexistent"
self.delete_par(
d,
invalid_par,
expect_status=400,
expect_content={"error": f"Paragraph {invalid_par} does not exist"},
)
self.delete_area(
d,
invalid_par,
par_id,
expect_status=400,
expect_content={"error": f"Paragraph {invalid_par} does not exist"},
)
self.delete_area(
d,
par_id,
invalid_par,
expect_status=400,
expect_content={"error": f"Paragraph {invalid_par} does not exist"},
)
self.get(
f"/getBlock/{d.id}/{invalid_par}",
expect_status=404,
expect_content="Paragraph not found. It may have been deleted.",
)
self.get(
f"/getBlock/{d.id}/{par_id}",
query_string={"area_start": par_id, "area_end": invalid_par},
expect_status=404,
expect_content="Area not found. It may have been deleted.",
)
[docs] def test_duplicate_task_ids(self):
self.login_test1()
d = self.create_doc()
r = self.new_par(d.document, "``` {#test plugin=showVideo}\n```")
self.assertEqual(r["duplicates"], [])
r = self.new_par(d.document, "``` {#test plugin=showVideo}\n```")
pars = d.document.get_paragraphs()
self.assertEqual(r["duplicates"], [["test", pars[1].get_id()]])
[docs] def test_area_editing(self):
self.login_test1()
d = self.create_doc(initial_par=["a1par", "a2par", "a3par"])
pars = d.document.get_paragraphs()
new_text = (
d.document.export_markdown()
.replace("a1par", "b1par")
.replace("a2par", "b2par")
.replace("a3par", "b3par")
)
self.json_post(
"/postParagraph/",
{
"text": new_text,
"docId": d.id,
"par": pars[0].get_id(),
"par_next": None,
"area_start": pars[0].get_id(),
"area_end": pars[-1].get_id(),
},
)
d.document.clear_mem_cache()
self.assertEqual(d.document.export_markdown(), new_text)
self.delete_area(d, pars[0].get_id(), pars[-1].get_id())
d.document.clear_mem_cache()
self.assertEqual(d.document.get_paragraphs(), [])
[docs] def test_get_updates_pars_translation(self):
self.login_test1()
d = self.create_doc(initial_par=["kissa"])
t = self.create_translation(d)
e = self.get(t.url, as_tree=True)
self.assert_content(e, ["kissa"])
tr_pars = t.document.get_paragraphs()
par = tr_pars[0]
md = par.get_exported_markdown().replace("kissa", "cat")
e = self.post_par(t.document, md, par.get_id(), as_tree=True, json_key="texts")
self.assert_content(e, ["cat"])
updated = self.get_updated_pars(t, json_key="changed_pars")
e = html.fromstring(updated[par.get_id()])
self.assert_content(e, ["cat"])
[docs] def test_update(self):
"""Editing a document with minor errors is possible in manage.
Document must not contain any errors after editing.
"""
self.login_test1()
d = self.create_doc()
for i in range(0, 2):
d.document.add_text("#- {area=a}")
d.document.add_text("#- {area_end=a}")
par_ids = d.document.get_par_ids()
self.update_whole_doc(
d,
f"""
#- {{area=a id={par_ids[0]}}}
#- {{area_end=a id={par_ids[1]}}}
#- {{area=b id={par_ids[2]}}}
#- {{area_end=b id={par_ids[3]}}}
""",
)
orig_text = d.document.export_markdown()
self.json_post(
f"/update/{d.id}",
{
"fulltext": f"""
#- {{area=a id={par_ids[0]}}}
#- {{area_end=a id={par_ids[1]}}}
#- {{area=a id={par_ids[2]}}}
#- {{area_end=a id={par_ids[3]}}}
""",
"original": orig_text,
},
expect_status=400,
expect_content=f"Multiple areas with same name noticed for area 'a' in paragraph {par_ids[2]}\nDuplicate area end noticed in paragraph {par_ids[3]}",
)
self.json_post(
f"/update/{d.id}",
{
"fulltext": f"""
#- {{id={par_ids[0]}}}
#- {{id={par_ids[0]}}}
""",
"original": orig_text,
},
expect_status=400,
expect_content=f"Duplicate paragraph id noticed in paragraph {par_ids[0]}",
)
self.json_post(
f"/update/{d.id}",
{
"fulltext": f"""
#- {{id=xxxx}}
""",
"original": orig_text,
},
expect_status=400,
expect_content=f"Invalid paragraph id noticed in paragraph xxxx",
)
self.json_post(
f"/update/{d.id}",
{
"fulltext": """
```
``` {a=b}
""",
"original": orig_text,
},
expect_status=400,
expect_contains=f"Attributes at end of code block noticed in paragraph ",
)
[docs] def test_new_from_help_par(self):
self.login_test1()
d = self.create_doc()
e = self.json_post(
f"/newParagraph/",
{"docId": d.id, "text": "test", "par": "HELP_PAR", "par_next": None},
json_key="texts",
as_tree=True,
)
self.assert_content(e, ["test"])
[docs] def test_duplicate_template(self):
"""Trying to load a template to a non-empty document is not possible."""
self.login_test1()
t = self.create_doc(initial_par=["p1", "p2"])
d = self.create_doc()
template_pars = t.document.get_paragraphs()
d.document.add_paragraph("p1", template_pars[0].get_id())
d.document.add_paragraph("p2", template_pars[1].get_id())
self.json_post(
f"/update/{d.id}",
{"template_name": t.path},
expect_status=400,
expect_content={
"error": "Cannot load a template because the document is not empty."
},
)
self.get(d.url)
[docs] def test_nonexistent_template(self):
"""Trying to load a non-existent template gives 404."""
self.login_test1()
d = self.create_doc()
self.json_post(
f"/update/{d.id}",
{"template_name": "xxx"},
expect_status=404,
expect_content={"error": "Template not found"},
)
[docs] def test_invalid_add(self):
self.login_test1()
d = self.create_doc()
self.new_par(d.document, "test", next_id="xxx", expect_status=400)
[docs] def test_invalid_update(self):
self.login_test1()
d = self.create_doc(initial_par="test")
par = d.document.get_paragraphs()[0]
md = d.document.export_markdown()
self.json_post(
f"/update/{d.id}",
{"fulltext": md, "original": ""},
expect_status=400,
expect_content={"error": f"Duplicate paragraph id(s): {par.get_id()}"},
)
self.get(d.url)
[docs] def test_duplicate_par_ids(self):
self.login_test1()
d = self.create_doc(initial_par=["test1", "test2"])
pars = d.document.get_paragraphs()
par1 = pars[0]
par2 = pars[1]
md1 = par1.get_exported_markdown(export_ids=True)
self.new_par(
d.document,
md1,
expect_status=400,
expect_content={"error": f"Duplicate paragraph id(s): {par1.get_id()}"},
)
self.post_par(d.document, md1, par_id=par1.get_id())
md2 = par2.get_exported_markdown(export_ids=True)
self.post_par(
d.document,
md1 + md2,
par_id=par1.get_id(),
expect_status=400,
expect_content={"error": f"Duplicate paragraph id(s): {par2.get_id()}"},
)
self.get(d.url)
[docs] def test_version(self):
self.login_test1()
d = self.create_doc()
j = self.new_par(d.document, "test")
self.assertEqual(j["version"], [1, 0])
[docs] def test_mark_read(self):
self.login_test1()
d = self.create_doc()
self.new_par(d.document, "test", additional_data={"tags": {"markread": True}})
[docs] def test_xml_encoding(self):
self.login_test1()
d = self.create_doc(
initial_par="""<?xml version="1.0" encoding="iso-8859-1"?>"""
)
self.get(d.url)
[docs] def test_edit_attribute(self):
self.login_test1()
d = self.create_doc(
initial_par=[
"test",
"#- {edit=manage}",
"test",
"#- {area=a edit=manage}",
"in area",
"#- {area_end=a}",
]
)
pars = d.document.get_paragraphs()
par1 = pars[0]
par_manage = pars[1]
par3 = pars[2]
self.post_par(d.document, "#- {edit=manage}\ntest", par_manage.get_id())
self.test_user_2.grant_access(d, AccessType.edit)
db.session.commit()
self.login_test2()
self.post_par(d.document, "asd", par1.get_id())
self.post_par(
d.document,
"testing",
par_manage.get_id(),
expect_status=403,
expect_content=f"Only users with manage access can edit this paragraph ({par_manage.get_id()}).",
)
self.update_whole_doc(d, "", expect_status=403)
self.post_area(d, "", par1.get_id(), par3.get_id(), expect_status=403)
self.delete_area(d, par1.get_id(), par3.get_id(), expect_status=403)
self.delete_area(d, par1.get_id(), par_manage.get_id(), expect_status=403)
self.delete_area(d, par_manage.get_id(), par3.get_id(), expect_status=403)
self.delete_area(d, par_manage.get_id(), par_manage.get_id(), expect_status=403)
self.delete_par(d, par_manage.get_id(), expect_status=403)
self.cut(d, par1, par3, expect_status=403)
self.cut(d, par1, par_manage, expect_status=403)
self.cut(d, par_manage, par3, expect_status=403)
self.cut(d, par_manage, par_manage, expect_status=403)
self.json_post(
"/postNewTaskNames/",
{"docId": d.id, "duplicates": [[None, None, par_manage.get_id()]]},
expect_status=403,
)
self.json_post(
"/cancelChanges/",
{
"docId": d.id,
"originalPar": {"md": "asd"},
"parId": par_manage.get_id(),
"newPars": [],
},
expect_status=403,
)
self.json_post(
"/cancelChanges/",
{
"docId": d.id,
"originalPar": None,
"parId": par_manage.get_id(),
"newPars": [par_manage.get_id()],
},
expect_status=403,
)
self.json_post(f"/unwrap_area/{d.id}/a", expect_status=403)
self.test_user_2.grant_access(d, AccessType.manage)
db.session.commit()
self.post_par(d.document, "#- {edit=manage}\nedited", par1.get_id())
self.update_whole_doc(d, d.document.export_markdown() + "new")
self.post_area(
d,
d.document.export_section(par1.get_id(), par3.get_id()) + "new",
par1.get_id(),
par3.get_id(),
)
self.delete_area(d, par1.get_id(), par3.get_id())
self.json_post(f"/unwrap_area/{d.id}/a")
[docs] def test_download(self):
self.login_test1()
d = self.create_doc(initial_par="test")
par_id = d.document.get_paragraphs()[0].get_id()
self.get(
f"/download/{d.id}",
expect_content=f"""
#- {{id="{par_id}"}}
test
""".strip()
+ "\n",
)
self.get(
f"/download/{d.id}",
query_string={"format": "json"},
expect_content=d.document.export_raw_data(),
)
self.login_test2()
self.get(f"/download/{d.id}", expect_status=403)
self.get(
f"/download/{d.id}", query_string={"format": "json"}, expect_status=403
)
[docs] def test_download_with_tls(self):
self.login_test1()
d1 = self.create_doc(initial_par="Original text")
d1_par = d1.document.get_last_par()
d2 = self.create_translation(d1)
d2_par = d2.document.get_last_par()
self.get(
f"/download/{d2.id}",
query_string={"with_tl": "true"},
expect_content=f"""#- {{r="tr" rp="{d1_par.id}" id="{d2_par.id}"}}
Original text
""",
)
[docs] def test_no_unnecessary_update(self):
self.login_test1()
d = self.create_doc(initial_par="test")
self.get(d.url)
self.new_par(
d.document,
"""
#- {settings=""}
macros:
a: b""",
next_id=d.document.get_paragraphs()[0].get_id(),
)
r = self.get_updated_pars(d)
self.assertFalse(r["changed_pars"])
[docs] def test_no_unnecessary_update_with_preamble(self):
self.login_test1()
d = self.create_doc(initial_par="test")
p = self.create_preamble_for(d, initial_par="pr")
self.get(d.url)
pid = d.document.get_paragraphs()[0].get_id()
self.post_par(d.document, """hi""", par_id=pid)
r = self.get_updated_pars(d)
self.assertEqual({pid}, set(r["changed_pars"].keys()))
[docs] def test_no_area_start_update(self):
"""getUpdatedPars shouldn't return area start or end pars because it will break the DOM structure."""
self.login_test1()
d = self.create_doc(
initial_par="""
#- {area=a collapse=true}
title
#-
1
#- {area_end=a}
"""
)
self.get_updated_pars(d) # refresh cache
pars = d.document.get_paragraphs()
md = d.document.export_markdown().replace("title", "titleedit")
self.post_area(d, md, area_start=pars[0].get_id(), area_end=pars[-1].get_id())
r = self.get_updated_pars(d)
self.assertEqual({}, r["changed_pars"])
[docs] def test_html_heading_same_as_md(self):
self.login_test1()
d = self.create_doc(
initial_par="""
# x
#-
<h1>x</h1>
"""
)
e = self.get(d.url, as_tree=True)
content = e.cssselect(".parContent")
p1: HtmlElement = content[0].getchildren()[0]
p2: HtmlElement = content[1].getchildren()[0]
self.assertEqual("x", p1.get("id"))
self.assertEqual("x-1", p2.get("id"))
[docs] def test_getblock(self):
self.login_test1()
d = self.create_doc(
initial_par="""
#- {plugin=pali}
#- {defaultplugin=pali}
#-
test
"""
)
d_id = d.id
self.test_user_2.grant_access(d, AccessType.view)
db.session.commit()
par_ids = [p.get_id() for p in d.document.get_paragraphs()]
self.login_test2()
self.get(f"/getBlock/{d_id}/{par_ids[0]}", expect_status=403)
self.get(f"/getBlock/{d_id}/{par_ids[1]}", expect_status=403)
self.get(f"/getBlock/{d_id}/{par_ids[2]}")
[docs]class TimTableEditTest(TimRouteTest):
[docs] def test_timtable_edit(self):
self.login_test1()
d = self.create_doc(
initial_par="""
``` {plugin="timTable"}
table:
countRow: 1
countCol: 1
rows:
- row:
- cell: 'hi'
```"""
)
par = d.document.get_paragraphs()[0]
par_id = par.get_id()
ver1 = d.document.get_version()
self.json_post(
f"/timTable/saveCell",
{
"cellContent": "hey",
"docId": d.id,
"parId": par_id,
"row": 0,
"col": 0,
},
)
d.document.clear_mem_cache()
ver2 = d.document.get_version()
self.assertNotEqual(ver1, ver2)
par = d.document.get_paragraphs()[0]
p = Plugin.from_paragraph(par, default_view_ctx)
self.assertEqual(
{
"table": {
"countCol": 1,
"countRow": 1,
"rows": [{"row": [{"cell": "hi"}]}],
"tabledatablock": {"cells": {"A1": "hey"}, "type": "relative"},
}
},
p.values,
)
self.json_post(
f"/timTable/saveCell",
{
"cellContent": "hey",
"docId": d.id,
"parId": par_id,
"row": 0,
"col": 0,
},
)
d.document.clear_mem_cache()
ver3 = d.document.get_version()
self.assertEqual(ver2, ver3)