mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Merge branch 'pretix:master' into vite-vue3
This commit is contained in:
@@ -1177,6 +1177,30 @@ def test_store_failed(token_client, organizer, clist, event, order):
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_store_failed_after_success(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
p = order.positions.first()
|
||||
p.all_checkins.create(
|
||||
type=Checkin.TYPE_ENTRY,
|
||||
nonce='foobar',
|
||||
successful=True,
|
||||
list=clist,
|
||||
raw_barcode=p.secret
|
||||
)
|
||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/failed_checkins/'.format(
|
||||
organizer.slug, event.slug, clist.pk,
|
||||
), {
|
||||
'raw_barcode': p.secret,
|
||||
'nonce': 'foobar',
|
||||
'position': p.pk,
|
||||
'error_reason': 'unpaid'
|
||||
}, format='json')
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
assert Checkin.all.filter(position=p).count() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_redeem_unknown(token_client, organizer, clist, event, order):
|
||||
resp = _redeem(token_client, organizer, clist, 'unknown_secret', {'force': True})
|
||||
|
||||
@@ -32,21 +32,27 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
from decimal import Decimal
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
import pytest
|
||||
from django.conf import settings
|
||||
from django.core import mail as djmail
|
||||
from django.test import override_settings
|
||||
from django.utils.html import escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import scope
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.models import Event, Organizer, User
|
||||
from pretix.base.services.mail import mail
|
||||
from pretix.base.models import (
|
||||
Event, InvoiceAddress, Order, Organizer, OutgoingMail, User,
|
||||
)
|
||||
from pretix.base.services.mail import mail, mail_send_task
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -67,6 +73,45 @@ def env():
|
||||
yield event, user, o
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def item(env):
|
||||
return env[0].items.create(name="Budget Ticket", default_price=23)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@scopes_disabled()
|
||||
def order(env, item):
|
||||
event, _, _ = env
|
||||
o = Order.objects.create(
|
||||
code="FOO",
|
||||
event=event,
|
||||
email="dummy@dummy.test",
|
||||
status=Order.STATUS_PENDING,
|
||||
secret="k24fiuwvu8kxz3y1",
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
datetime=datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
expires=datetime.datetime(2017, 12, 10, 10, 0, 0, tzinfo=datetime.timezone.utc),
|
||||
total=23,
|
||||
locale="en",
|
||||
)
|
||||
o.positions.create(
|
||||
order=o,
|
||||
item=item,
|
||||
variation=None,
|
||||
price=Decimal("23"),
|
||||
attendee_email="peter@example.org",
|
||||
attendee_name_parts={"given_name": "Peter", "family_name": "Miller"},
|
||||
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
|
||||
pseudonymization_id="ABCDEFGHKL",
|
||||
)
|
||||
InvoiceAddress.objects.create(
|
||||
order=o,
|
||||
name_parts={"given_name": "Peter", "family_name": "Miller"},
|
||||
)
|
||||
return o
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_send_mail_with_prefix(env):
|
||||
djmail.outbox = []
|
||||
@@ -162,6 +207,96 @@ def test_send_mail_with_user_locale(env):
|
||||
assert 'The language code used for rendering this email is de.' in djmail.outbox[0].body
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_queue_state_sent(env):
|
||||
m = OutgoingMail.objects.create(
|
||||
to=['recipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
)
|
||||
assert m.status == OutgoingMail.STATUS_QUEUED
|
||||
mail_send_task.apply(kwargs={
|
||||
'outgoing_mail': m.pk,
|
||||
}, max_retries=0)
|
||||
m.refresh_from_db()
|
||||
assert m.status == OutgoingMail.STATUS_SENT
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(EMAIL_BACKEND='pretix.testutils.mail.PermanentlyFailingEmailBackend')
|
||||
def test_queue_state_permanent_failure(env):
|
||||
m = OutgoingMail.objects.create(
|
||||
to=['recipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
)
|
||||
assert m.status == OutgoingMail.STATUS_QUEUED
|
||||
mail_send_task.apply(kwargs={
|
||||
'outgoing_mail': m.pk,
|
||||
}, max_retries=0)
|
||||
m.refresh_from_db()
|
||||
assert m.status == OutgoingMail.STATUS_FAILED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(EMAIL_BACKEND='pretix.testutils.mail.FailingEmailBackend')
|
||||
def test_queue_state_retry_failure(env, monkeypatch):
|
||||
def retry(*args, **kwargs):
|
||||
raise Exception()
|
||||
|
||||
monkeypatch.setattr('celery.app.task.Task.retry', retry, raising=True)
|
||||
m = OutgoingMail.objects.create(
|
||||
to=['recipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
)
|
||||
assert m.status == OutgoingMail.STATUS_QUEUED
|
||||
mail_send_task.apply(kwargs={
|
||||
'outgoing_mail': m.pk,
|
||||
}, max_retries=0)
|
||||
m.refresh_from_db()
|
||||
assert m.status == OutgoingMail.STATUS_AWAITING_RETRY
|
||||
assert m.retry_after > now()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_queue_state_foreign_key_handling():
|
||||
o = Organizer.objects.create(name='Dummy', slug='dummy')
|
||||
event = Event.objects.create(
|
||||
organizer=o, name='Dummy', slug='dummy',
|
||||
date_from=now()
|
||||
)
|
||||
|
||||
mail_queued = OutgoingMail.objects.create(
|
||||
organizer=o,
|
||||
event=event,
|
||||
to=['recipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
)
|
||||
mail_sent = OutgoingMail.objects.create(
|
||||
organizer=o,
|
||||
event=event,
|
||||
to=['recipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
status=OutgoingMail.STATUS_SENT,
|
||||
)
|
||||
|
||||
event.delete()
|
||||
|
||||
assert not OutgoingMail.objects.filter(pk=mail_queued.pk).exists()
|
||||
assert OutgoingMail.objects.get(pk=mail_sent.pk).event is None
|
||||
|
||||
o.delete()
|
||||
assert not OutgoingMail.objects.filter(pk=mail_sent.pk).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sendmail_placeholder(env):
|
||||
djmail.outbox = []
|
||||
@@ -188,7 +323,7 @@ def _extract_html(mail):
|
||||
def test_placeholder_html_rendering_from_template(env):
|
||||
djmail.outbox = []
|
||||
event, user, organizer = env
|
||||
event.name = "<strong>event & co. kg</strong>"
|
||||
event.name = "<strong>event & co. kg</strong> {currency}"
|
||||
event.save()
|
||||
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', get_email_context(
|
||||
event=event,
|
||||
@@ -197,25 +332,26 @@ def test_placeholder_html_rendering_from_template(env):
|
||||
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [user.email]
|
||||
assert 'Event name: <strong>event & co. kg</strong>' in djmail.outbox[0].body
|
||||
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
|
||||
assert '**Meta**: *Beep*' in djmail.outbox[0].body
|
||||
assert 'Event website: [<strong>event & co. kg</strong>](https://example.org/dummy)' in djmail.outbox[0].body
|
||||
assert 'Other website: [<strong>event & co. kg</strong>](https://example.com)' in djmail.outbox[0].body
|
||||
assert '<' not in djmail.outbox[0].body
|
||||
assert '&' not in djmail.outbox[0].body
|
||||
# Known bug for now: These should not have HTML for the plain body, but we'll fix this safter the security release
|
||||
assert escape('Event name: <strong>event & co. kg</strong> {currency}') in djmail.outbox[0].body
|
||||
assert '<strong>IBAN</strong>: 123<br>\n<strong>BIC</strong>: 456' in djmail.outbox[0].body
|
||||
assert '**Meta**: <em>Beep</em>' in djmail.outbox[0].body
|
||||
assert escape('Event website: [<strong>event & co. kg</strong> {currency}](https://example.org/dummy)') in djmail.outbox[0].body
|
||||
# todo: assert '<' not in djmail.outbox[0].body
|
||||
# todo: assert '&' not in djmail.outbox[0].body
|
||||
assert 'Unevaluated placeholder: {currency}' in djmail.outbox[0].body
|
||||
assert 'EUR' not in djmail.outbox[0].body
|
||||
html = _extract_html(djmail.outbox[0])
|
||||
|
||||
assert '<strong>event' not in html
|
||||
assert 'Event name: <strong>event & co. kg</strong>' in html
|
||||
assert 'Event name: <strong>event & co. kg</strong> {currency}' in html
|
||||
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
|
||||
assert '<strong>Meta</strong>: <em>Beep</em>' in html
|
||||
assert 'Unevaluated placeholder: {currency}' in html
|
||||
assert 'EUR' not in html
|
||||
assert re.search(
|
||||
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank"><strong>event & co. kg</strong></a>',
|
||||
html
|
||||
)
|
||||
assert re.search(
|
||||
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank"><strong>event & co. kg</strong></a>',
|
||||
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">'
|
||||
r'<strong>event & co. kg</strong> {currency}</a>',
|
||||
html
|
||||
)
|
||||
|
||||
@@ -233,7 +369,7 @@ def test_placeholder_html_rendering_from_string(env):
|
||||
})
|
||||
djmail.outbox = []
|
||||
event, user, organizer = env
|
||||
event.name = "<strong>event & co. kg</strong>"
|
||||
event.name = "<strong>event & co. kg</strong> {currency}"
|
||||
event.save()
|
||||
ctx = get_email_context(
|
||||
event=event,
|
||||
@@ -244,9 +380,9 @@ def test_placeholder_html_rendering_from_string(env):
|
||||
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [user.email]
|
||||
assert 'Event name: <strong>event & co. kg</strong>' in djmail.outbox[0].body
|
||||
assert 'Event website: [<strong>event & co. kg</strong>](https://example.org/dummy)' in djmail.outbox[0].body
|
||||
assert 'Other website: [<strong>event & co. kg</strong>](https://example.com)' in djmail.outbox[0].body
|
||||
assert 'Event name: <strong>event & co. kg</strong> {currency}' in djmail.outbox[0].body
|
||||
assert 'Event website: [<strong>event & co. kg</strong> {currency}](https://example.org/dummy)' in djmail.outbox[0].body
|
||||
assert 'Other website: [<strong>event & co. kg</strong> {currency}](https://example.com)' in djmail.outbox[0].body
|
||||
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
|
||||
assert '**Meta**: *Beep*' in djmail.outbox[0].body
|
||||
assert 'URL: https://google.com' in djmail.outbox[0].body
|
||||
@@ -261,11 +397,13 @@ def test_placeholder_html_rendering_from_string(env):
|
||||
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
|
||||
assert '<strong>Meta</strong>: <em>Beep</em>' in html
|
||||
assert re.search(
|
||||
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank"><strong>event & co. kg</strong></a>',
|
||||
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">'
|
||||
r'<strong>event & co. kg</strong> {currency}</a>',
|
||||
html
|
||||
)
|
||||
assert re.search(
|
||||
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank"><strong>event & co. kg</strong></a>',
|
||||
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank">'
|
||||
r'<strong>event & co. kg</strong> {currency}</a>',
|
||||
html
|
||||
)
|
||||
assert re.search(
|
||||
@@ -286,3 +424,141 @@ def test_placeholder_html_rendering_from_string(env):
|
||||
r'style="[^"]+" target="_blank">Link & Text</a>',
|
||||
html
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_nested_placeholder_inclusion_full_process(env, order):
|
||||
# Test that it is not possible to sneak in a placeholder like {url_cancel} inside a user-controlled
|
||||
# placeholder value like {invoice_company}
|
||||
event, user, organizer = env
|
||||
position = order.positions.get()
|
||||
order.invoice_address.company = "{url_cancel} Corp"
|
||||
order.invoice_address.save()
|
||||
event.settings.mail_text_resend_link = LazyI18nString({"en": "Ticket for {invoice_company}"})
|
||||
event.settings.mail_subject_resend_link_attendee = LazyI18nString({"en": "Ticket for {invoice_company}"})
|
||||
|
||||
djmail.outbox = []
|
||||
position.resend_link()
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [position.attendee_email]
|
||||
assert "Ticket for {url_cancel} Corp" == djmail.outbox[0].subject
|
||||
assert "/cancel" not in djmail.outbox[0].body
|
||||
assert "/order" not in djmail.outbox[0].body
|
||||
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
|
||||
for part in (html, plain):
|
||||
assert "Ticket for {url_cancel} Corp" in part
|
||||
assert "/order/" not in part
|
||||
assert "/cancel" not in part
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_nested_placeholder_inclusion_mail_service(env):
|
||||
# test that it is not possible to have placeholders within the values of placeholders when
|
||||
# the mail() function is called directly
|
||||
template = LazyI18nString("Event name: {event}")
|
||||
djmail.outbox = []
|
||||
event, user, organizer = env
|
||||
event.name = "event & {currency} co. kg"
|
||||
event.slug = "event-co-ag-slug"
|
||||
event.save()
|
||||
|
||||
mail(
|
||||
"dummy@dummy.dummy",
|
||||
"{event} Test subject",
|
||||
template,
|
||||
get_email_context(
|
||||
event=event,
|
||||
payment_info="**IBAN**: 123 \n**BIC**: 456 {event}",
|
||||
),
|
||||
event,
|
||||
)
|
||||
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [user.email]
|
||||
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
|
||||
for part in (html, plain, djmail.outbox[0].subject):
|
||||
assert "event & {currency} co. kg" in part or "event & {currency} co. kg" in part
|
||||
assert "EUR" not in part
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("tpl", [
|
||||
"Event: {event.__class__}",
|
||||
"Event: {{event.__class__}}",
|
||||
"Event: {{{event.__class__}}}",
|
||||
])
|
||||
def test_variable_inclusion_from_string_full_process(env, tpl, order):
|
||||
# Test that it is not possible to use placeholders that leak system information in templates
|
||||
# when run through system processes
|
||||
event, user, organizer = env
|
||||
event.name = "event & co. kg"
|
||||
event.save()
|
||||
position = order.positions.get()
|
||||
event.settings.mail_text_resend_link = LazyI18nString({"en": tpl})
|
||||
event.settings.mail_subject_resend_link_attendee = LazyI18nString({"en": tpl})
|
||||
|
||||
position.resend_link()
|
||||
assert len(djmail.outbox) == 1
|
||||
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
|
||||
for part in (html, plain, djmail.outbox[0].subject):
|
||||
assert "{event.__class__}" in part
|
||||
assert "LazyI18nString" not in part
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("tpl", [
|
||||
"Event: {event.__class__}",
|
||||
"Event: {{event.__class__}}",
|
||||
"Event: {{{event.__class__}}}",
|
||||
])
|
||||
def test_variable_inclusion_from_string_mail_service(env, tpl):
|
||||
# Test that it is not possible to use placeholders that leak system information in templates
|
||||
# when run through mail() directly
|
||||
event, user, organizer = env
|
||||
event.name = "event & co. kg"
|
||||
event.save()
|
||||
|
||||
djmail.outbox = []
|
||||
mail(
|
||||
"dummy@dummy.dummy",
|
||||
tpl,
|
||||
LazyI18nString(tpl),
|
||||
get_email_context(
|
||||
event=event,
|
||||
payment_info="**IBAN**: 123 \n**BIC**: 456\n" + tpl,
|
||||
),
|
||||
event,
|
||||
)
|
||||
assert len(djmail.outbox) == 1
|
||||
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
|
||||
for part in (html, plain, djmail.outbox[0].subject):
|
||||
assert "{event.__class__}" in part
|
||||
assert "LazyI18nString" not in part
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_escaped_braces_mail_services(env):
|
||||
# Test that braces can be escaped by doubling
|
||||
template = LazyI18nString("Event name: -{{currency}}-")
|
||||
djmail.outbox = []
|
||||
event, user, organizer = env
|
||||
event.name = "event & co. kg"
|
||||
event.save()
|
||||
|
||||
mail(
|
||||
"dummy@dummy.dummy",
|
||||
"-{{currency}}- Test subject",
|
||||
template,
|
||||
get_email_context(
|
||||
event=event,
|
||||
payment_info="**IBAN**: 123 \n**BIC**: 456 {event}",
|
||||
),
|
||||
event,
|
||||
)
|
||||
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == [user.email]
|
||||
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
|
||||
for part in (html, plain, djmail.outbox[0].subject):
|
||||
assert "EUR" not in part
|
||||
assert "-{currency}-" in part
|
||||
|
||||
@@ -170,7 +170,7 @@ def test_price_mode_validation(event, item, user):
|
||||
import_vouchers.apply(
|
||||
args=(event.pk, inputfile_factory().id, settings, 'en', user.pk)
|
||||
).get()
|
||||
assert 'It is pointless to set a value without a price mode.' in str(excinfo.value)
|
||||
assert 'It is pointless to set a value without a price effect.' in str(excinfo.value)
|
||||
|
||||
settings['price_mode'] = 'static:percent'
|
||||
import_vouchers.apply(
|
||||
|
||||
@@ -43,7 +43,7 @@ def _validate_sample_lines(sample_lines, rounding_mode):
|
||||
(line.tax_value_includes_rounding_correction, line.price_includes_rounding_correction)
|
||||
for line in sample_lines
|
||||
]
|
||||
changed = apply_rounding(rounding_mode, "EUR", sample_lines)
|
||||
changed = apply_rounding(rounding_mode, None, "EUR", sample_lines)
|
||||
for line, original in zip(sample_lines, corrections):
|
||||
if (line.tax_value_includes_rounding_correction, line.price_includes_rounding_correction) != original:
|
||||
assert line in changed
|
||||
@@ -108,7 +108,7 @@ def test_revert_net_rounding_to_single_line(sample_lines):
|
||||
tax_rate=Decimal("19.00"),
|
||||
tax_code="S",
|
||||
)
|
||||
apply_rounding("sum_by_net", "EUR", [l])
|
||||
apply_rounding("sum_by_net", None, "EUR", [l])
|
||||
assert l.price == Decimal("100.00")
|
||||
assert l.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert l.tax_value == Decimal("15.97")
|
||||
@@ -126,7 +126,7 @@ def test_revert_net_keep_gross_rounding_to_single_line(sample_lines):
|
||||
tax_rate=Decimal("19.00"),
|
||||
tax_code="S",
|
||||
)
|
||||
apply_rounding("sum_by_net_keep_gross", "EUR", [l])
|
||||
apply_rounding("sum_by_net_keep_gross", None, "EUR", [l])
|
||||
assert l.price == Decimal("100.00")
|
||||
assert l.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert l.tax_value == Decimal("15.97")
|
||||
@@ -144,7 +144,7 @@ def test_rounding_of_impossible_gross_price(rounding_mode):
|
||||
price=Decimal("23.00"),
|
||||
)
|
||||
l._calculate_tax(tax_rule=TaxRule(rate=Decimal("7.00")), invoice_address=InvoiceAddress())
|
||||
apply_rounding(rounding_mode, "EUR", [l])
|
||||
apply_rounding(rounding_mode, None, "EUR", [l])
|
||||
assert l.price == Decimal("23.01")
|
||||
assert l.price_includes_rounding_correction == Decimal("0.01")
|
||||
assert l.tax_value == Decimal("1.51")
|
||||
@@ -164,12 +164,12 @@ def test_round_down():
|
||||
assert sum(l.tax_value for l in lines) == Decimal("79.85")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("420.15")
|
||||
|
||||
apply_rounding("sum_by_net", "EUR", lines)
|
||||
apply_rounding("sum_by_net", None, "EUR", lines)
|
||||
assert sum(l.price for l in lines) == Decimal("499.98")
|
||||
assert sum(l.tax_value for l in lines) == Decimal("79.83")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("420.15")
|
||||
|
||||
apply_rounding("sum_by_net_keep_gross", "EUR", lines)
|
||||
apply_rounding("sum_by_net_keep_gross", None, "EUR", lines)
|
||||
assert sum(l.price for l in lines) == Decimal("500.00")
|
||||
assert sum(l.tax_value for l in lines) == Decimal("79.83")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("420.17")
|
||||
@@ -187,12 +187,12 @@ def test_round_up():
|
||||
assert sum(l.tax_value for l in lines) == Decimal("79.80")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("420.10")
|
||||
|
||||
apply_rounding("sum_by_net", "EUR", lines)
|
||||
apply_rounding("sum_by_net", None, "EUR", lines)
|
||||
assert sum(l.price for l in lines) == Decimal("499.92")
|
||||
assert sum(l.tax_value for l in lines) == Decimal("79.82")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("420.10")
|
||||
|
||||
apply_rounding("sum_by_net_keep_gross", "EUR", lines)
|
||||
apply_rounding("sum_by_net_keep_gross", None, "EUR", lines)
|
||||
assert sum(l.price for l in lines) == Decimal("499.90")
|
||||
assert sum(l.tax_value for l in lines) == Decimal("79.82")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("420.08")
|
||||
@@ -210,12 +210,12 @@ def test_round_currency_without_decimals():
|
||||
assert sum(l.tax_value for l in lines) == Decimal("7980.00")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("42010.00")
|
||||
|
||||
apply_rounding("sum_by_net", "JPY", lines)
|
||||
apply_rounding("sum_by_net", None, "JPY", lines)
|
||||
assert sum(l.price for l in lines) == Decimal("49992.00")
|
||||
assert sum(l.tax_value for l in lines) == Decimal("7982.00")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("42010.00")
|
||||
|
||||
apply_rounding("sum_by_net_keep_gross", "JPY", lines)
|
||||
apply_rounding("sum_by_net_keep_gross", None, "JPY", lines)
|
||||
assert sum(l.price for l in lines) == Decimal("49990.00")
|
||||
assert sum(l.tax_value for l in lines) == Decimal("7982.00")
|
||||
assert sum(l.price - l.tax_value for l in lines) == Decimal("42008.00")
|
||||
@@ -235,7 +235,7 @@ def test_do_not_touch_free(rounding_mode):
|
||||
price=Decimal("23.00"),
|
||||
)
|
||||
l2._calculate_tax(tax_rule=TaxRule(rate=Decimal("7.00")), invoice_address=InvoiceAddress())
|
||||
apply_rounding(rounding_mode, "EUR", [l1, l2])
|
||||
apply_rounding(rounding_mode, None, "EUR", [l1, l2])
|
||||
assert l2.price == Decimal("23.01")
|
||||
assert l2.price_includes_rounding_correction == Decimal("0.01")
|
||||
assert l2.tax_value == Decimal("1.51")
|
||||
@@ -245,3 +245,20 @@ def test_do_not_touch_free(rounding_mode):
|
||||
assert l1.price_includes_rounding_correction == Decimal("0.00")
|
||||
assert l1.tax_value == Decimal("0.00")
|
||||
assert l1.tax_value_includes_rounding_correction == Decimal("0.00")
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_only_business():
|
||||
lines = [OrderPosition(
|
||||
price=Decimal("100.00"),
|
||||
tax_value=Decimal("15.97"),
|
||||
tax_rate=Decimal("19.00"),
|
||||
tax_code="S",
|
||||
) for _ in range(5)]
|
||||
assert sum(l.price for l in lines) == Decimal("500.00")
|
||||
|
||||
apply_rounding("sum_by_net_only_business", None, "EUR", lines)
|
||||
assert sum(l.price for l in lines) == Decimal("500.00")
|
||||
|
||||
apply_rounding("sum_by_net_only_business", InvoiceAddress(is_business=True), "EUR", lines)
|
||||
assert sum(l.price for l in lines) == Decimal("499.98")
|
||||
|
||||
@@ -31,7 +31,7 @@ from django_scopes import scope
|
||||
|
||||
from pretix.base.models import (
|
||||
CachedCombinedTicket, CachedTicket, Event, InvoiceAddress, Order,
|
||||
OrderPayment, OrderPosition, Organizer, QuestionAnswer,
|
||||
OrderPayment, OrderPosition, Organizer, OutgoingMail, QuestionAnswer,
|
||||
)
|
||||
from pretix.base.services.invoices import generate_invoice, invoice_pdf_task
|
||||
from pretix.base.services.tickets import generate
|
||||
@@ -111,6 +111,15 @@ def test_email_shredder(event, order):
|
||||
'new_email': 'foo@bar.com',
|
||||
}
|
||||
)
|
||||
m = OutgoingMail.objects.create(
|
||||
event=event,
|
||||
order=order,
|
||||
to=['recipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
headers={},
|
||||
)
|
||||
|
||||
s = EmailAddressShredder(event)
|
||||
f = list(s.generate_files())
|
||||
@@ -129,6 +138,7 @@ def test_email_shredder(event, order):
|
||||
assert 'Foo' not in l1.data
|
||||
l2.refresh_from_db()
|
||||
assert '@' not in l2.data
|
||||
assert not OutgoingMail.objects.filter(pk=m.pk).exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
||||
@@ -24,12 +24,13 @@ from smtplib import SMTPResponseException
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.test.utils import override_settings
|
||||
from django_scopes import scopes_disabled
|
||||
from tests.base import SoupTest, extract_form_fields
|
||||
|
||||
from pretix.base.models import Event, Organizer, Team, User
|
||||
from pretix.base.models import Event, Organizer, OutgoingMail, Team, User
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -453,3 +454,111 @@ class OrganizerTest(SoupTest):
|
||||
self.event1.refresh_from_db()
|
||||
assert 'pretix.plugins.banktransfer' not in self.event1.get_plugins()
|
||||
assert 'pretix.plugins.stripe' in self.event1.get_plugins()
|
||||
|
||||
def test_outgoing_mails_list_and_detail(self):
|
||||
m1 = OutgoingMail.objects.create(
|
||||
organizer=self.orga1,
|
||||
to=['rightrecipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
headers={},
|
||||
)
|
||||
m2 = OutgoingMail.objects.create(
|
||||
organizer=self.orga2,
|
||||
to=['wrongrecipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
headers={},
|
||||
)
|
||||
resp = self.client.get('/control/organizer/%s/outgoingmails' % self.orga1.slug)
|
||||
assert resp.status_code == 200
|
||||
assert b"rightrecipient@example.com" in resp.content
|
||||
assert b"wrongrecipient@example.com" not in resp.content
|
||||
|
||||
resp = self.client.get('/control/organizer/%s/outgoingmails?status=queued' % self.orga1.slug)
|
||||
assert resp.status_code == 200
|
||||
assert b"rightrecipient@example.com" in resp.content
|
||||
resp = self.client.get('/control/organizer/%s/outgoingmails?status=sent' % self.orga1.slug)
|
||||
assert resp.status_code == 200
|
||||
assert b"rightrecipient@example.com" not in resp.content
|
||||
|
||||
if 'postgresql' in settings.DATABASES['default']['ENGINE']:
|
||||
resp = self.client.get('/control/organizer/%s/outgoingmails?query=RIGHTrecipient@example.com' % self.orga1.slug)
|
||||
assert resp.status_code == 200
|
||||
assert b"rightrecipient@example.com" in resp.content
|
||||
resp = self.client.get('/control/organizer/%s/outgoingmails?query=wrongrecipient@example.com' % self.orga1.slug)
|
||||
assert resp.status_code == 200
|
||||
assert b"rightrecipient@example.com" not in resp.content
|
||||
|
||||
resp = self.client.get('/control/organizer/%s/outgoingmail/%d/' % (self.orga1.slug, m1.pk))
|
||||
assert resp.status_code == 200
|
||||
assert b"rightrecipient@example.com" in resp.content
|
||||
|
||||
resp = self.client.get('/control/organizer/%s/outgoingmail/%d/' % (self.orga1.slug, m2.pk))
|
||||
assert resp.status_code == 404
|
||||
|
||||
def test_outgoing_mails_retry(self):
|
||||
m1 = OutgoingMail.objects.create(
|
||||
organizer=self.orga1,
|
||||
status=OutgoingMail.STATUS_SENT,
|
||||
to=['rightrecipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
headers={},
|
||||
)
|
||||
m2 = OutgoingMail.objects.create(
|
||||
organizer=self.orga1,
|
||||
status=OutgoingMail.STATUS_FAILED,
|
||||
to=['rightrecipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
headers={},
|
||||
)
|
||||
resp = self.client.post(
|
||||
'/control/organizer/%s/outgoingmail/bulk_action' % self.orga1.slug,
|
||||
data={
|
||||
"action": "retry",
|
||||
"outgoingmail": [m1.pk, m2.pk]
|
||||
}
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
m1.refresh_from_db()
|
||||
m2.refresh_from_db()
|
||||
assert m1.status == OutgoingMail.STATUS_SENT
|
||||
assert m2.status in (OutgoingMail.STATUS_SENT, OutgoingMail.STATUS_QUEUED)
|
||||
|
||||
def test_outgoing_mails_abort(self):
|
||||
m1 = OutgoingMail.objects.create(
|
||||
organizer=self.orga1,
|
||||
status=OutgoingMail.STATUS_SENT,
|
||||
to=['rightrecipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
headers={},
|
||||
)
|
||||
m2 = OutgoingMail.objects.create(
|
||||
organizer=self.orga1,
|
||||
status=OutgoingMail.STATUS_QUEUED,
|
||||
to=['rightrecipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test',
|
||||
sender='sender@example.com',
|
||||
headers={},
|
||||
)
|
||||
resp = self.client.post(
|
||||
'/control/organizer/%s/outgoingmail/bulk_action' % self.orga1.slug,
|
||||
data={
|
||||
"action": "abort",
|
||||
"__ALL": "on",
|
||||
}
|
||||
)
|
||||
assert resp.status_code == 302
|
||||
m1.refresh_from_db()
|
||||
m2.refresh_from_db()
|
||||
assert m1.status == OutgoingMail.STATUS_SENT
|
||||
assert m2.status == OutgoingMail.STATUS_ABORTED
|
||||
|
||||
@@ -192,6 +192,9 @@ organizer_urls = [
|
||||
'organizer/abc/team/1/edit',
|
||||
'organizer/abc/team/1/delete',
|
||||
'organizer/abc/team/add',
|
||||
'organizer/abc/outgoingmails',
|
||||
'organizer/abc/outgoingmail/bulk_action',
|
||||
'organizer/abc/outgoingmail/1/',
|
||||
'organizer/abc/devices',
|
||||
'organizer/abc/device/add',
|
||||
'organizer/abc/device/bulk_edit',
|
||||
@@ -528,6 +531,9 @@ organizer_permission_urls = [
|
||||
("can_change_organizer_settings", "organizer/dummy/settings/plugins/pretix.plugins.sendmail/events", 200),
|
||||
("can_change_organizer_settings", "organizer/dummy/settings/email", 200),
|
||||
("can_change_organizer_settings", "organizer/dummy/settings/email/setup", 200),
|
||||
("can_change_organizer_settings", "organizer/dummy/outgoingmails", 200),
|
||||
("can_change_organizer_settings", "organizer/dummy/outgoingmail/1/", 404),
|
||||
("can_change_organizer_settings", "organizer/dummy/outgoingmail/bulk_action", 405),
|
||||
("can_change_organizer_settings", "organizer/dummy/devices", 200),
|
||||
("can_change_organizer_settings", "organizer/dummy/devices/select2", 200),
|
||||
("can_change_organizer_settings", "organizer/dummy/device/add", 200),
|
||||
|
||||
@@ -339,13 +339,17 @@ class UserSettings2FATest(SoupTest):
|
||||
|
||||
def test_gen_emergency(self):
|
||||
self.client.get('/control/settings/2fa/')
|
||||
assert not StaticDevice.objects.filter(user=self.user, name='emergency').exists()
|
||||
|
||||
self.client.post('/control/settings/2fa/regenemergency')
|
||||
d = StaticDevice.objects.get(user=self.user, name='emergency')
|
||||
assert d.token_set.count() == 10
|
||||
old_tokens = set(t.token for t in d.token_set.all())
|
||||
|
||||
self.client.post('/control/settings/2fa/regenemergency')
|
||||
new_tokens = set(t.token for t in d.token_set.all())
|
||||
d = StaticDevice.objects.get(user=self.user, name='emergency')
|
||||
assert d.token_set.count() == 10
|
||||
new_tokens = set(t.token for t in d.token_set.all())
|
||||
assert old_tokens != new_tokens
|
||||
|
||||
def test_delete_u2f(self):
|
||||
|
||||
@@ -29,6 +29,8 @@ def test_format_map():
|
||||
assert format_map("Foo {baz}", {"bar": 3}) == "Foo {baz}"
|
||||
assert format_map("Foo {bar.__module__}", {"bar": 3}) == "Foo {bar.__module__}"
|
||||
assert format_map("Foo {bar!s}", {"bar": 3}) == "Foo 3"
|
||||
assert format_map("Foo {bar!r}", {"bar": '3'}) == "Foo 3"
|
||||
assert format_map("Foo {bar!a}", {"bar": '3'}) == "Foo 3"
|
||||
assert format_map("Foo {bar:<20}", {"bar": 3}) == "Foo 3"
|
||||
|
||||
|
||||
|
||||
@@ -164,6 +164,7 @@ def test_org_resetpw(env, client):
|
||||
customer.refresh_from_db()
|
||||
assert customer.check_password('PANioMR62')
|
||||
assert customer.is_verified
|
||||
assert len(djmail.outbox) == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -623,6 +624,7 @@ def test_change_email(env, client):
|
||||
customer.refresh_from_db()
|
||||
assert customer.email == 'john@example.org'
|
||||
assert len(djmail.outbox) == 1
|
||||
assert djmail.outbox[0].to == ['john@example.com']
|
||||
|
||||
token = dumps({
|
||||
'customer': customer.pk,
|
||||
@@ -632,6 +634,9 @@ def test_change_email(env, client):
|
||||
assert r.status_code == 302
|
||||
customer.refresh_from_db()
|
||||
assert customer.email == 'john@example.com'
|
||||
assert len(djmail.outbox) == 3
|
||||
assert djmail.outbox[1].to == ['john@example.org']
|
||||
assert djmail.outbox[2].to == ['john@example.com']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -673,6 +678,7 @@ def test_change_pw(env, client, client2):
|
||||
|
||||
r = client.get('/bigevents/account/password')
|
||||
assert r.status_code == 200
|
||||
assert len(djmail.outbox) == 1
|
||||
|
||||
# Client 2 got logged out
|
||||
r = client2.post('/bigevents/account/password')
|
||||
|
||||
@@ -1015,7 +1015,8 @@ class OrderChangeAddonsTest(BaseOrdersTest):
|
||||
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert 'Workshop 1' not in response.content.decode()
|
||||
assert '<li>1x Workshop 1</li>' in response.content.decode()
|
||||
assert f'cp_{self.ticket_pos.pk}_item_{self.workshop1.pk}' not in response.content.decode()
|
||||
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
@@ -1035,6 +1036,39 @@ class OrderChangeAddonsTest(BaseOrdersTest):
|
||||
with scopes_disabled():
|
||||
assert self.ticket_pos.addons.count() == 2
|
||||
|
||||
def test_do_not_overbook_unavailable_on_adding(self):
|
||||
self.iao.max_count = 1
|
||||
self.iao.save()
|
||||
self.workshop1.available_until = now() - datetime.timedelta(days=1)
|
||||
self.workshop1.save()
|
||||
with scopes_disabled():
|
||||
OrderPosition.objects.create(
|
||||
order=self.order,
|
||||
item=self.workshop1,
|
||||
variation=None,
|
||||
price=Decimal("12"),
|
||||
addon_to=self.ticket_pos,
|
||||
attendee_name_parts={'full_name': "Peter"}
|
||||
)
|
||||
self.order.total += Decimal("12")
|
||||
self.order.save()
|
||||
|
||||
response = self.client.get(
|
||||
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret)
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert '<li>1x Workshop 1</li>' in response.content.decode()
|
||||
assert f'cp_{self.ticket_pos.pk}_item_{self.workshop1.pk}' not in response.content.decode()
|
||||
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/change' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret),
|
||||
{
|
||||
f'cp_{self.ticket_pos.pk}_variation_{self.workshop2.pk}_{self.workshop2a.pk}': '1'
|
||||
},
|
||||
follow=True
|
||||
)
|
||||
assert 'alert-danger' in response.content.decode()
|
||||
|
||||
def test_remove_addon_checked_in(self):
|
||||
with scopes_disabled():
|
||||
self.event.settings.change_allow_user_if_checked_in = True
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{% load i18n %}
|
||||
This is a test file for sending mails.
|
||||
Event name: {event}
|
||||
Event name: {{ event }}
|
||||
Unevaluated placeholder: {currency}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
The language code used for rendering this email is {{ LANGUAGE_CODE }}.
|
||||
|
||||
Payment info:
|
||||
{payment_info}
|
||||
{{ payment_info }}
|
||||
|
||||
**Meta**: {meta_Test}
|
||||
**Meta**: {{ meta_Test }}
|
||||
|
||||
Event website: [{event}](https://example.org/{event_slug})
|
||||
Other website: [{event}]({meta_Website})
|
||||
Event website: [{{event}}](https://example.org/{{event_slug}})
|
||||
Reference in New Issue
Block a user