from timApp.auth.accesstype import AccessType
from timApp.tests.server.timroutetest import TimRouteTest
from timApp.timdb.sqa import db
from timApp.user.user import User
[docs]class InlinePluginTest(TimRouteTest):
[docs] def test_inline_plugins(self):
self.login_test1()
d = self.create_doc(
initial_par="""
#- {defaultplugin=pali id=Lm7y6R7n5XIb}
*Hello* {#t1#}, {#tx:nonexistent#} and {#t2#} $x$
#- {defaultplugin=pali math_type=svg id=spOMcE20X2aX}
Hi {#t3#} $x$
#- {defaultplugin=pali id=Se0s8FDLbhOp}
{#t4 header: hi, footer: ho#}
"""
)
r = self.get(d.url, as_tree=True)
e = r.cssselect(".par")[0]
expected_json = self.create_plugin_json(
d,
"t1",
"Lm7y6R7n5XIb",
)
expected_json2 = self.create_plugin_json(
d,
"t2",
"Lm7y6R7n5XIb",
)
self.assert_same_html(
e,
rf"""
<div class="par" id="Lm7y6R7n5XIb" t="MHgyN2U5NDhhMA==" attrs='{{"defaultplugin": "pali"}}'>
<div tabindex="0" class="parContent">
<p>
<em>Hello</em> <tim-plugin-loader type="full" answer-id="" class="pluginpali inlineplugin" task-id="{d.id}.t1"><span id="{d.id}.t1.Lm7y6R7n5XIb" data-plugin="/pali">
<pali-runner json="{self.make_base64(
expected_json)}"></pali-runner></span></tim-plugin-loader>, <span class="error" ng-non-bindable>Plugin nonexistent error: Plugin does not exist.</span> and <tim-plugin-loader type="full" answer-id="" class="pluginpali inlineplugin" task-id="{d.id}.t2"><span id="{d.id}.t2.Lm7y6R7n5XIb" data-plugin="/pali"><pali-runner json="{self.make_base64(
expected_json2)}"></pali-runner></span></tim-plugin-loader>
<span class="math inline">\(x\)</span>
</p>
</div>
<div class="editline" tabindex="0" title="Click to edit this paragraph"></div>
<div class="readline" title="Click to mark this paragraph as read"></div>
</div>
""",
)
a = self.post_answer_no_abdata(
plugin_type="pali",
task_id=f"{d.id}.t2",
user_input={"userword": "aaaaaa"},
)
aid = a["savedNew"]
self.assertEqual(
{"savedNew": aid, "valid": True, "web": {"result": "saved"}}, a
)
self.assertIsInstance(aid, int)
r = self.get(d.url, as_tree=True)
e = r.cssselect(".par")[0]
s = {"userword": "aaaaaa"}
expected_json2 = self.create_plugin_json(
d,
"t2",
"Lm7y6R7n5XIb",
info={
"earlier_answers": 1,
"look_answer": False,
"max_answers": None,
"user_id": "testuser1",
"valid": True,
},
state=s,
)
self.assert_same_html(
e,
rf"""
<div class="par" id="Lm7y6R7n5XIb" t="MHgyN2U5NDhhMA==" attrs='{{"defaultplugin": "pali"}}'>
<div tabindex="0" class="parContent">
<p>
<em>Hello</em>
<tim-plugin-loader type="full" answer-id="" class="pluginpali inlineplugin" task-id="{d.id}.t1">
<span id="{d.id}.t1.Lm7y6R7n5XIb" data-plugin="/pali">
<pali-runner json="{self.make_base64(expected_json)}"></pali-runner></span></tim-plugin-loader>,
<span class="error" ng-non-bindable>Plugin nonexistent error: Plugin does not exist.</span> and
<tim-plugin-loader type="full" answer-id="{aid}" class="pluginpali inlineplugin" task-id="{d.id}.t2">
<span id="{d.id}.t2.Lm7y6R7n5XIb" data-plugin="/pali"><pali-runner json="{self.make_base64(
expected_json2)}"></pali-runner></span></tim-plugin-loader>
<span class="math inline">\(x\)</span>
</p>
</div>
<div class="editline" tabindex="0" title="Click to edit this paragraph"></div>
<div class="readline" title="Click to mark this paragraph as read"></div>
</div>
""",
)
expected_json = self.create_plugin_json(
d,
"t3",
"spOMcE20X2aX",
)
self.assert_same_html(
r.cssselect(".par")[1],
f"""
<div class="par" id="spOMcE20X2aX" t="LTB4MTk4ZmYxOTQ=" attrs='{{"defaultplugin": "pali", "math_type": "svg"}}'>
<div tabindex="0" class="parContent">
<p>
Hi
<tim-plugin-loader type="full" answer-id="" class="pluginpali inlineplugin" task-id="{d.id}.t3">
<span id="{d.id}.t3.spOMcE20X2aX" data-plugin="/pali">
<pali-runner json="{self.make_base64(expected_json)}"></pali-runner>
</span>
</tim-plugin-loader>
<span class="mathp inline"><img style="width:0.80327em; vertical-align:-0.06000em" src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0nVVRGLTgnPz4KPCEtLSBUaGlzIGZpbGUgd2FzIGdlbmVyYXRlZCBieSBkdmlzdmdtIDIuMTIgLS0+CjxzdmcgdmVyc2lvbj0nMS4xJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB3aWR0aD0nNi42OTM5MjJwdCcgaGVpZ2h0PSc1LjI4OTQ2cHQnIHZpZXdCb3g9Jy0uNTAwMDAyIC00Ljc4OTQ1OCA2LjY5MzkyMiA1LjI4OTQ2Jz4KPGRlZnM+CjxzdHlsZSB0eXBlPSd0ZXh0L2Nzcyc+CjwhW0NEQVRBW3BhdGgge3N0cm9rZTogY3VycmVudENvbG9yO3N0cm9rZS13aWR0aDogMC4wNXB0O31dXT4KPC9zdHlsZT4KPHBhdGggaWQ9J2cwLTEyMCcgZD0nTTMuMzI3NTIyLTMuMDA4NzE3QzMuMzg3Mjk4LTMuMjY3NzQ2IDMuNjE2NDM4LTQuMTg0MzA5IDQuMzEzODIzLTQuMTg0MzA5QzQuMzYzNjM2LTQuMTg0MzA5IDQuNjAyNzQtNC4xODQzMDkgNC44MTE5NTUtNC4wNTQ3OTVDNC41MzMwMDEtNC4wMDQ5ODEgNC4zMzM3NDgtMy43NTU5MTUgNC4zMzM3NDgtMy41MTY4MTJDNC4zMzM3NDgtMy4zNTc0MSA0LjQ0MzMzNy0zLjE2ODEyIDQuNzEyMzI5LTMuMTY4MTJDNC45MzE1MDctMy4xNjgxMiA1LjI1MDMxMS0zLjM0NzQ0NyA1LjI1MDMxMS0zLjc0NTk1M0M1LjI1MDMxMS00LjI2NDAxIDQuNjYyNTE2LTQuNDAzNDg3IDQuMzIzNzg2LTQuNDAzNDg3QzMuNzQ1OTUzLTQuNDAzNDg3IDMuMzk3MjYtMy44NzU0NjcgMy4yNzc3MDktMy42NDYzMjZDMy4wMjg2NDMtNC4zMDM4NjEgMi40OTA2Ni00LjQwMzQ4NyAyLjIwMTc0My00LjQwMzQ4N0MxLjE2NTYyOS00LjQwMzQ4NyAuNTk3NzU4LTMuMTE4MzA2IC41OTc3NTgtMi44NjkyNEMuNTk3NzU4LTIuNzY5NjE0IC42OTczODUtMi43Njk2MTQgLjcxNzMxLTIuNzY5NjE0Qy43OTcwMTEtMi43Njk2MTQgLjgyNjg5OS0yLjc4OTUzOSAuODQ2ODI0LTIuODc5MjAzQzEuMTg1NTU0LTMuOTM1MjQzIDEuODQzMDg4LTQuMTg0MzA5IDIuMTgxODE4LTQuMTg0MzA5QzIuMzcxMTA4LTQuMTg0MzA5IDIuNzE5ODAxLTQuMDk0NjQ1IDIuNzE5ODAxLTMuNTE2ODEyQzIuNzE5ODAxLTMuMjA3OTcgMi41NTA0MzYtMi41NDA0NzMgMi4xODE4MTgtMS4xNDU3MDRDMi4wMjI0MTYtLjUyODAyIDEuNjczNzI0LS4xMDk1ODkgMS4yMzUzNjctLjEwOTU4OUMxLjE3NTU5Mi0uMTA5NTg5IC45NDY0NTEtLjEwOTU4OSAuNzM3MjM1LS4yMzkxMDNDLjk4NjMwMS0uMjg4OTE3IDEuMjA1NDc5LS40OTgxMzIgMS4yMDU0NzktLjc3NzA4NkMxLjIwNTQ3OS0xLjA0NjA3NyAuOTg2MzAxLTEuMTI1Nzc4IC44MzY4NjItMS4xMjU3NzhDLjUzNzk4My0xLjEyNTc3OCAuMjg4OTE3LS44NjY3NSAuMjg4OTE3LS41NDc5NDVDLjI4ODkxNy0uMDg5NjY0IC43ODcwNDkgLjEwOTU4OSAxLjIyNTQwNSAuMTA5NTg5QzEuODgyOTM5IC4xMDk1ODkgMi4yNDE1OTQtLjU4Nzc5NiAyLjI3MTQ4Mi0uNjQ3NTcyQzIuMzkxMDM0LS4yNzg5NTQgMi43NDk2ODkgLjEwOTU4OSAzLjM0NzQ0NyAuMTA5NTg5QzQuMzczNTk5IC4xMDk1ODkgNC45NDE0NjktMS4xNzU1OTIgNC45NDE0NjktMS40MjQ2NThDNC45NDE0NjktMS41MjQyODQgNC44NTE4MDYtMS41MjQyODQgNC44MjE5MTgtMS41MjQyODRDNC43MzIyNTQtMS41MjQyODQgNC43MTIzMjktMS40ODQ0MzMgNC42OTI0MDMtMS40MTQ2OTVDNC4zNjM2MzYtLjM0ODY5MiAzLjY4NjE3Ny0uMTA5NTg5IDMuMzY3MzcyLS4xMDk1ODlDMi45Nzg4MjktLjEwOTU4OSAyLjgxOTQyNy0uNDI4Mzk0IDIuODE5NDI3LS43NjcxMjNDMi44MTk0MjctLjk4NjMwMSAyLjg3OTIwMy0xLjIwNTQ3OSAyLjk4ODc5Mi0xLjY0MzgzNkwzLjMyNzUyMi0zLjAwODcxN1onLz4KPC9kZWZzPgo8ZyBpZD0ncGFnZTEnPgo8dXNlIHg9JzAnIHk9JzAnIHhsaW5rOmhyZWY9JyNnMC0xMjAnLz4KPC9nPgo8L3N2Zz4=" title="x"></span>
</p>
</div>
<div class="editline" tabindex="0" title="Click to edit this paragraph"></div>
<div class="readline" title="Click to mark this paragraph as read"></div>
</div>
""",
)
expected_json = self.create_plugin_json(
d,
"t4",
"Se0s8FDLbhOp",
markup={
"header": "hi",
"footer": "ho",
},
)
self.assert_same_html(
r.cssselect(".par")[2],
f"""
<div class="par" id="Se0s8FDLbhOp" t="LTB4NGU3YzFkYWM=" attrs='{{"defaultplugin": "pali"}}'>
<div tabindex="0" class="parContent">
<p>
<tim-plugin-loader type="full" answer-id="" class="pluginpali inlineplugin" task-id="{d.id}.t4">
<span id="{d.id}.t4.Se0s8FDLbhOp" data-plugin="/pali">
<pali-runner json="{self.make_base64(expected_json)}"></pali-runner>
</span>
</tim-plugin-loader>
</p>
</div>
<div class="editline" tabindex="0" title="Click to edit this paragraph"></div>
<div class="readline" title="Click to mark this paragraph as read"></div>
</div>
""",
)
a = self.post_answer_no_abdata(
plugin_type="pali",
task_id=f"{d.id}.t5",
user_input={"userword": "aaaaaa"},
expect_status=400,
expect_content="Task not found in the document: t5",
)
[docs] def test_inline_plugin_no_html_escape(self):
self.login_test1()
d = self.create_doc(
initial_par="""
#- {defaultplugin=pali id=a3Xuyg1PF1l1}
{#t5 initword: #}
"""
)
r = self.get(d.url, as_tree=True)
# Make sure Dumbo won't escape plugin's error HTML.
self.assert_same_html(
r.cssselect(".parContent")[0],
f"""
<div tabindex="0" class="parContent">
<p>
<tim-plugin-loader type="full" answer-id="" class="pluginpali inlineplugin" task-id="{d.id}.t5">
<span id="{d.id}.t5.a3Xuyg1PF1l1" data-plugin="/pali">
<div class="pluginError">
The following fields have invalid values:
<ul>
<li>
initword: Field may not be null.
</li>
</ul>
</div>
</span>
</tim-plugin-loader>
</p>
</div>
""",
)
[docs] def test_inline_plugin_sanitize(self):
self.login_test1()
d = self.create_doc(
initial_par="""
#- {defaultplugin=pali}
<script>alert('hello')</script>
"""
)
r = self.get(d.url, as_tree=True)
self.assertFalse(r.cssselect(".parContent script"))
[docs] def test_multiline_inlineplugin(self):
"""Multiline markup in inlineplugins works."""
self.login_test1()
d = self.create_doc(
initial_par="""
#- {defaultplugin=pali id=SSYigUyqdb7p}
{#t
initword: hi
#}
"""
)
r = self.get(d.url, as_tree=True)
e = r.cssselect(
f'.parContent > p > tim-plugin-loader[task-id="{d.id}.t"] > span > pali-runner'
)
self.assertTrue(e)
self.assert_plugin_json(
e[0],
self.create_plugin_json(
d,
"t",
par_id="SSYigUyqdb7p",
markup={"initword": "hi"},
),
)
d = self.create_doc(
initial_par="""
#- {defaultplugin=pali id=SSYigUyqdb7p}
{#t
initword: hi
inputplaceholder: test
#}
"""
)
r = self.get(d.url, as_tree=True)
e = r.cssselect(
f'.parContent > p > tim-plugin-loader[task-id="{d.id}.t"] > span > pali-runner'
)
self.assertTrue(e)
self.assert_plugin_json(
e[0],
self.create_plugin_json(
d,
"t",
par_id="SSYigUyqdb7p",
markup={
"initword": "hi",
"inputplaceholder": "test",
},
),
)
[docs] def test_inline_plugin_error_html_no_p(self):
self.login_test1()
d = self.create_doc(
initial_par="""
#- {defaultplugin="pali" id=a3Xuyg1PF1l1}
a {#x initword: #} b
"""
)
r = self.get(d.url, as_tree=True)
self.assert_same_html(
r.cssselect(".parContent")[0],
f"""
<div tabindex="0" class="parContent">
<p>
a
<tim-plugin-loader type="full" answer-id="" class="pluginpali inlineplugin" task-id="{d.id}.x">
<span id="{d.id}.x.a3Xuyg1PF1l1" data-plugin="/pali">
<div class="pluginError">
The following fields have invalid values:
<ul>
<li>
initword: Field may not be null.
</li>
</ul>
</div>
</span>
</tim-plugin-loader>
b
</p>
</div>""",
)
[docs] def test_inline_plugin_ref(self):
self.login_test1()
d = self.create_doc(
initial_par="""
#- {defaultplugin=pali id=SSYigUyqdb7p}
{#t}
"""
)
d2 = self.create_doc()
d2.document.add_paragraph_obj(
d.document.get_paragraphs()[0].create_reference(d2.document)
)
self.get(d2.url)
[docs] def test_inline_plugin_login(self):
self.login_test1()
d = self.create_doc(
initial_par="""
#- {defaultplugin=pali}
{#t#}
"""
)
u = d.url
User.get_anon().grant_access(d, AccessType.view)
db.session.commit()
self.logout()
r = self.get(u, as_tree=True).cssselect(".parContent tim-login-menu")
self.assertTrue(r)