From 9696a1dee8589ac30d183d964bbc8ebf941e0c98 Mon Sep 17 00:00:00 2001 From: Daniel Grams Date: Tue, 7 Sep 2021 22:48:11 +0200 Subject: [PATCH] Veranstaltung melden #290 --- .coveragerc | 3 +- cypress/integration/event.js | 18 +- project/access.py | 16 +- project/api/event/resources.py | 27 +- project/api/event/schemas.py | 14 + project/services/user.py | 8 + .../static/vue/common/validated-input.vue.js | 53 +++ .../vue/common/validated-textarea.vue.js | 53 +++ project/static/vue/event-report/create.vue.js | 116 ++++++ .../vue/organization-relations/update.vue.js | 2 + project/templates/_macros.html | 2 + .../templates/email/event_report_notice.html | 9 + .../templates/email/event_report_notice.txt | 6 + project/templates/event/report.html | 22 ++ project/templates/layout_vue.html | 334 +++++++++++++++++ project/templates/manage/relations.html | 346 ++---------------- project/views/event.py | 60 ++- project/views/reference_request.py | 17 +- project/views/reference_request_review.py | 31 +- project/views/utils.py | 6 + project/views/widget.py | 34 +- tests/api/test_event.py | 28 ++ tests/utils.py | 22 +- tests/views/test_event.py | 8 + 24 files changed, 838 insertions(+), 397 deletions(-) create mode 100644 project/static/vue/common/validated-input.vue.js create mode 100644 project/static/vue/common/validated-textarea.vue.js create mode 100644 project/static/vue/event-report/create.vue.js create mode 100644 project/templates/email/event_report_notice.html create mode 100644 project/templates/email/event_report_notice.txt create mode 100644 project/templates/event/report.html create mode 100644 project/templates/layout_vue.html diff --git a/.coveragerc b/.coveragerc index 9cd4d42..a9e5ed4 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,4 @@ [run] omit = - project/cli/test.py \ No newline at end of file + project/cli/test.py + project/templates/email/* \ No newline at end of file diff --git a/cypress/integration/event.js b/cypress/integration/event.js index 8ad6794..84ebb6b 100644 --- a/cypress/integration/event.js +++ b/cypress/integration/event.js @@ -77,7 +77,6 @@ describe("Event", () => { }); it("read and actions", () => { - cy.login(); cy.createAdminUnit().then(function (adminUnitId) { cy.createEvent(adminUnitId).then(function (eventId) { cy.visit("/event/" + eventId); @@ -89,6 +88,23 @@ describe("Event", () => { }); }); + it("report", () => { + cy.createAdminUnit().then(function (adminUnitId) { + cy.createEvent(adminUnitId).then(function (eventId) { + cy.visit("/event/" + eventId + "/report"); + + cy.get("input[name=contactName]").type("Firstname Lastname"); + cy.get("input[name=contactEmail]").type("firstname.lastname@test.de"); + cy.get("textarea[name=message]").type("Die Veranstaltung kann leider nicht stattfinden."); + cy.screenshot("report"); + cy.get("button[type=submit]").click(); + + cy.get('button[type=submit]').should('not.exist'); + cy.screenshot("report-submitted"); + }); + }); + }); + it("deletes", () => { cy.login(); cy.createAdminUnit().then(function (adminUnitId) { diff --git a/project/access.py b/project/access.py index 133bbfb..2bc5965 100644 --- a/project/access.py +++ b/project/access.py @@ -7,7 +7,7 @@ from flask_security.utils import FsPermNeed from sqlalchemy import and_ from project import app -from project.models import AdminUnit, AdminUnitMember, Event, PublicStatus +from project.models import AdminUnit, AdminUnitMember, Event, PublicStatus, User from project.services.admin_unit import get_member_for_admin_unit_by_user_id @@ -206,3 +206,17 @@ def can_read_event_or_401(event: Event): def can_read_private_events(admin_unit: AdminUnit) -> bool: return has_access(admin_unit, "event:read") + + +def get_admin_unit_members_with_permission(admin_unit_id: int, permission: str) -> list: + members = ( + AdminUnitMember.query.join(User) + .filter(AdminUnitMember.admin_unit_id == admin_unit_id) + .all() + ) + + return list( + filter( + lambda member: has_admin_unit_member_permission(member, permission), members + ) + ) diff --git a/project/api/event/resources.py b/project/api/event/resources.py index ecc9601..9654bc0 100644 --- a/project/api/event/resources.py +++ b/project/api/event/resources.py @@ -1,4 +1,4 @@ -from flask import make_response +from flask import make_response, request from flask_apispec import doc, marshal_with, use_kwargs from sqlalchemy.orm import lazyload, load_only @@ -10,12 +10,13 @@ from project.access import ( login_api_user, login_api_user_or_401, ) -from project.api import add_api_resource +from project.api import add_api_resource, rest_api from project.api.event.schemas import ( EventListRequestSchema, EventListResponseSchema, EventPatchRequestSchema, EventPostRequestSchema, + EventReportPostSchema, EventSchema, EventSearchRequestSchema, EventSearchResponseSchema, @@ -25,6 +26,7 @@ from project.api.event_date.schemas import ( EventDateListResponseSchema, ) from project.api.resources import BaseResource +from project.api.schemas import NoneSchema from project.models import AdminUnit, Event, EventDate, PublicStatus from project.oauth2 import require_oauth from project.services.event import ( @@ -34,7 +36,10 @@ from project.services.event import ( update_event, ) from project.services.event_search import EventSearchParams -from project.views.event import send_referenced_event_changed_mails +from project.views.event import ( + send_event_report_mails, + send_referenced_event_changed_mails, +) def api_can_read_event_or_401(event: Event): @@ -154,7 +159,23 @@ class EventSearchResource(BaseResource): return pagination +class EventReportsResource(BaseResource): + @doc(summary="Add event report", tags=["Events"]) + @use_kwargs(EventReportPostSchema, location="json", apply=False) + @marshal_with(NoneSchema, 204) + def post(self, id): + event = Event.query.options( + load_only(Event.id, Event.public_status) + ).get_or_404(id) + api_can_read_event_or_401(event) + send_event_report_mails(event, request.json) + return make_response("", 204) + + add_api_resource(EventListResource, "/events", "api_v1_event_list") add_api_resource(EventResource, "/events/", "api_v1_event") add_api_resource(EventDatesResource, "/events//dates", "api_v1_event_dates") add_api_resource(EventSearchResource, "/events/search", "api_v1_event_search") +rest_api.add_resource( + EventReportsResource, "/events//reports", endpoint="api_v1_event_reports" +) diff --git a/project/api/event/schemas.py b/project/api/event/schemas.py index bcd0508..87793e2 100644 --- a/project/api/event/schemas.py +++ b/project/api/event/schemas.py @@ -295,3 +295,17 @@ class EventPatchRequestSchema( self.make_patch_schema() photo = Owned(ImagePatchRequestSchema) + + +class EventReportPostSchema(marshmallow.Schema): + contact_name = fields.Str( + required=True, + validate=validate.Length(min=5, max=255), + ) + contact_email = fields.Email( + required=True, validate=[validate.Email(), validate.Length(max=255)] + ) + message = fields.Str( + required=True, + validate=validate.Length(min=20, max=1000), + ) diff --git a/project/services/user.py b/project/services/user.py index 44c2f0e..801ec12 100644 --- a/project/services/user.py +++ b/project/services/user.py @@ -1,6 +1,8 @@ from flask_security import hash_password +from sqlalchemy.orm import joinedload from project import user_datastore +from project.models import Role, User def create_user(email, password): @@ -52,3 +54,9 @@ def find_user_by_email(email): def get_user(id): return user_datastore.find_user(id=id) + + +def find_all_users_with_role(role_name: str) -> list: + return ( + User.query.options(joinedload(User.roles)).filter(Role.name == role_name).all() + ) diff --git a/project/static/vue/common/validated-input.vue.js b/project/static/vue/common/validated-input.vue.js new file mode 100644 index 0000000..57e8f9f --- /dev/null +++ b/project/static/vue/common/validated-input.vue.js @@ -0,0 +1,53 @@ +const ValidatedInput = { + template: ` +
+ + + + + {{ validationContext.errors[0] }} + + + +
+ `, + props: { + vid: { + type: String + }, + rules: { + type: [Object, String], + default: "" + }, + value: { + type: null + } + }, + data: () => ({ + innerValue: "" + }), + watch: { + // Handles internal model changes. + innerValue(newVal) { + this.$emit("input", newVal); + }, + // Handles external model changes. + value(newVal) { + this.innerValue = newVal; + } + }, + created() { + if (this.value) { + this.innerValue = this.value; + } + }, + methods: { + getValidationState({ dirty, validated, valid = null }) { + return dirty || validated ? valid : null; + }, + }, +}; diff --git a/project/static/vue/common/validated-textarea.vue.js b/project/static/vue/common/validated-textarea.vue.js new file mode 100644 index 0000000..b62d3ba --- /dev/null +++ b/project/static/vue/common/validated-textarea.vue.js @@ -0,0 +1,53 @@ +const ValidatedTextarea = { + template: ` +
+ + + + + {{ validationContext.errors[0] }} + + + +
+ `, + props: { + vid: { + type: String + }, + rules: { + type: [Object, String], + default: "" + }, + value: { + type: null + } + }, + data: () => ({ + innerValue: "" + }), + watch: { + // Handles internal model changes. + innerValue(newVal) { + this.$emit("input", newVal); + }, + // Handles external model changes. + value(newVal) { + this.innerValue = newVal; + } + }, + created() { + if (this.value) { + this.innerValue = this.value; + } + }, + methods: { + getValidationState({ dirty, validated, valid = null }) { + return dirty || validated ? valid : null; + }, + }, +}; diff --git a/project/static/vue/event-report/create.vue.js b/project/static/vue/event-report/create.vue.js new file mode 100644 index 0000000..37e1e6e --- /dev/null +++ b/project/static/vue/event-report/create.vue.js @@ -0,0 +1,116 @@ +const EventReportCreate = { + template: ` +
+

{{ $t("comp.title") }}

+ +
+

{{ event.name }}

+ + + + + + + + {{ $t("shared.submit") }} + + + +
+
+
+ `, + i18n: { + messages: { + en: { + comp: { + title: "Report event", + successMessage: "Event successfully reported", + }, + }, + de: { + comp: { + title: "Veranstaltung melden", + successMessage: "Veranstaltung erfolgreich gemeldet", + }, + }, + }, + }, + data: () => ({ + isLoading: false, + isSubmitting: false, + isSubmitted: false, + event: null, + form: { + contactName: "", + contactEmail: "", + message: "", + }, + }), + computed: { + eventId() { + return this.$route.params.event_id; + }, + eventUrl() { + return `/event/${this.eventId}`; + } + }, + mounted() { + this.isLoading = false; + this.event = null; + this.form = { + contactName: "", + contactEmail: "", + message: "", + }; + this.isSubmitted = false; + this.loadFormData(); + }, + methods: { + loadFormData() { + axios + .get(`/api/v1/events/${this.eventId}`, { + handleLoading: this.handleLoading, + }) + .then((response) => { + this.event = response.data; + }); + }, + handleLoading(isLoading) { + this.isLoading = isLoading; + }, + getValidationState({ dirty, validated, valid = null }) { + return dirty || validated ? valid : null; + }, + submitForm() { + const data = { + name: this.form.name, + }; + axios + .post(`/api/v1/events/${this.eventId}/reports`, data, { + handleLoading: this.handleSubmitting, + }) + .then(() => { + this.$root.makeSuccessToast(this.$t("comp.successMessage")); + this.isSubmitted = true; + }); + }, + handleSubmitting(isLoading) { + this.isSubmitting = isLoading; + }, + }, +}; diff --git a/project/static/vue/organization-relations/update.vue.js b/project/static/vue/organization-relations/update.vue.js index 6d4ab87..550af3b 100644 --- a/project/static/vue/organization-relations/update.vue.js +++ b/project/static/vue/organization-relations/update.vue.js @@ -56,6 +56,8 @@ const OrganizationRelationUpdate = { }, }, mounted() { + this.isLoading = false; + this.relation = null; this.form = { auto_verify_event_reference_requests: false, }; diff --git a/project/templates/_macros.html b/project/templates/_macros.html index a4925ac..462ab4d 100644 --- a/project/templates/_macros.html +++ b/project/templates/_macros.html @@ -460,6 +460,7 @@ @@ -615,6 +616,7 @@
{{ render_audit(event, show_rating) }} + {{ _('Report event') }}
diff --git a/project/templates/email/event_report_notice.html b/project/templates/email/event_report_notice.html new file mode 100644 index 0000000..d4b01fe --- /dev/null +++ b/project/templates/email/event_report_notice.html @@ -0,0 +1,9 @@ +{% extends "email/layout.html" %} +{% from "_macros.html" import render_email_button %} +{% block content %} +

{{ _('There is a new event report.') }}

+

{{ report['contact_name'] }}

+

{{ report['contact_email'] }}

+

{{ report['message'] }}

+{{ render_email_button(url_for('event', event_id=event.id, _external=True), _('Click here to view the event')) }} +{% endblock %} \ No newline at end of file diff --git a/project/templates/email/event_report_notice.txt b/project/templates/email/event_report_notice.txt new file mode 100644 index 0000000..f7b4d76 --- /dev/null +++ b/project/templates/email/event_report_notice.txt @@ -0,0 +1,6 @@ +{{ _('There is a new event report.') }} +{{ _('Click the link below to view the event') }} +{{ report['contact_name'] }} +{{ report['contact_email'] }} +{{ report['message'] }} +{{ url_for('event', event_id=event.id, _external=True) }} \ No newline at end of file diff --git a/project/templates/event/report.html b/project/templates/event/report.html new file mode 100644 index 0000000..ed90e2f --- /dev/null +++ b/project/templates/event/report.html @@ -0,0 +1,22 @@ +{% extends "layout_vue.html" %} + +{%- block title -%} +{{ _('Report event') }} +{%- endblock -%} + +{% block component_scripts %} + +{% endblock %} + +{% block component_definitions %} +Vue.component("EventReportCreate", EventReportCreate); +{% endblock %} + +{% block vue_routes %} +const routes = [ + { + path: "/event/:event_id/report", + component: EventReportCreate, + }, +]; +{% endblock %} diff --git a/project/templates/layout_vue.html b/project/templates/layout_vue.html new file mode 100644 index 0000000..b24a6d8 --- /dev/null +++ b/project/templates/layout_vue.html @@ -0,0 +1,334 @@ +{% extends "layout.html" %} + +{% block header_before_site_js %} + + +{% if False | env_override('FLASK_DEBUG') %} + +{% else %} + +{% endif %} + + + + + + + + + + + + + +{% block component_scripts %} +{% endblock %} +{% endblock %} + +{% block content %} +
+ +{% endblock %} diff --git a/project/templates/manage/relations.html b/project/templates/manage/relations.html index 368a1d4..64013f4 100644 --- a/project/templates/manage/relations.html +++ b/project/templates/manage/relations.html @@ -1,330 +1,34 @@ -{% extends "layout.html" %} +{% extends "layout_vue.html" %} {%- block title -%} {{ _('Relations') }} {%- endblock -%} -{% block header_before_site_js %} - - -{% if False | env_override('FLASK_DEBUG') %} - -{% else %} - -{% endif %} - - - - - - - - - +{% block component_scripts %} - -{% endblock %} {% block scripts %} {%- endblock scripts %} {% block content %} -
- +{% endblock %} + +{% block component_definitions %} +Vue.component("OrganizationRelationList", OrganizationRelationList); +Vue.component("OrganizationRelationCreate", OrganizationRelationCreate); +Vue.component("OrganizationRelationUpdate", OrganizationRelationUpdate); +{% endblock %} + +{% block vue_routes %} +const routes = [ + { + path: "/manage/admin_unit/:admin_unit_id/relations", + component: OrganizationRelationList, + }, + { + path: "/manage/admin_unit/:admin_unit_id/relations/create", + component: OrganizationRelationCreate, + }, + { + path: "/manage/admin_unit/:admin_unit_id/relations/:relation_id/update", + component: OrganizationRelationUpdate, + }, +]; {% endblock %} diff --git a/project/views/event.py b/project/views/event.py index 4895ff6..81c0a8b 100644 --- a/project/views/event.py +++ b/project/views/event.py @@ -12,15 +12,14 @@ from project.access import ( can_read_event_or_401, can_reference_event, can_request_event_reference, + get_admin_unit_members_with_permission, has_access, - has_admin_unit_member_permission, ) from project.dateutils import get_next_full_hour from project.forms.event import CreateEventForm, DeleteEventForm, UpdateEventForm from project.jsonld import DateTimeEncoder, get_sd_for_event_date from project.models import ( AdminUnit, - AdminUnitMember, Event, EventCategory, EventOrganizer, @@ -29,7 +28,6 @@ from project.models import ( EventReviewStatus, EventSuggestion, PublicStatus, - User, ) from project.services.event import ( get_event_with_details_or_404, @@ -48,7 +46,7 @@ from project.views.utils import ( get_share_links, handleSqlError, non_match_for_deletion, - send_mail, + send_mails, ) @@ -96,6 +94,14 @@ def event_actions(event_id): ) +@app.route("/event//report") +def event_report(event_id): + event = Event.query.get_or_404(event_id) + can_read_event_or_401(event) + + return render_template("event/report.html") + + @app.route("/admin_unit//events/create", methods=("GET", "POST")) @auth_required() def event_create_for_admin_unit_id(id): @@ -380,18 +386,38 @@ def send_referenced_event_changed_mails(event): for reference in references: # Alle Mitglieder der AdminUnit, die das Recht haben, Requests zu verifizieren - members = ( - AdminUnitMember.query.join(User) - .filter(AdminUnitMember.admin_unit_id == reference.admin_unit_id) - .all() + members = get_admin_unit_members_with_permission( + reference.admin_unit_id, "reference_request:verify" + ) + emails = list(map(lambda member: member.user.email, members)) + + send_mails( + emails, + gettext("Referenced event changed"), + "referenced_event_changed_notice", + event=event, + reference=reference, ) - for member in members: - if has_admin_unit_member_permission(member, "reference_request:verify"): - send_mail( - member.user.email, - gettext("Referenced event changed"), - "referenced_event_changed_notice", - event=event, - reference=reference, - ) + +def send_event_report_mails(event: Event, report: dict): + from project.services.user import find_all_users_with_role + + # Alle Mitglieder der AdminUnit, die das Recht haben, Events zu bearbeiten + members = get_admin_unit_members_with_permission( + event.admin_unit_id, "event:update" + ) + emails = list(map(lambda member: member.user.email, members)) + + # Alle globalen Admins + admins = find_all_users_with_role("admin") + admin_emails = list(map(lambda admin: admin.email, admins)) + emails.extend(x for x in admin_emails if x not in emails) + + send_mails( + emails, + gettext("New event report"), + "event_report_notice", + event=event, + report=report, + ) diff --git a/project/views/reference_request.py b/project/views/reference_request.py index ca0c168..ebcda07 100644 --- a/project/views/reference_request.py +++ b/project/views/reference_request.py @@ -8,16 +8,14 @@ from project import app, db from project.access import ( access_or_401, get_admin_unit_for_manage_or_404, + get_admin_unit_members_with_permission, get_admin_units_for_event_reference_request, - has_admin_unit_member_permission, ) from project.forms.reference_request import CreateEventReferenceRequestForm from project.models import ( - AdminUnitMember, Event, EventReferenceRequest, EventReferenceRequestReviewStatus, - User, ) from project.services.admin_unit import get_admin_unit_relation from project.services.reference import ( @@ -28,7 +26,7 @@ from project.views.utils import ( flash_errors, get_pagination_urls, handleSqlError, - send_mail, + send_mails, ) @@ -130,15 +128,12 @@ def send_member_reference_request_verify_mails( admin_unit_id, subject, template, **context ): # Benachrichtige alle Mitglieder der AdminUnit, die Requests verifizieren können - members = ( - AdminUnitMember.query.join(User) - .filter(AdminUnitMember.admin_unit_id == admin_unit_id) - .all() + members = get_admin_unit_members_with_permission( + admin_unit_id, "reference_request:verify" ) + emails = list(map(lambda member: member.user.email, members)) - for member in members: - if has_admin_unit_member_permission(member, "reference_request:verify"): - send_mail(member.user.email, subject, template, **context) + send_mails(emails, subject, template, **context) def send_reference_request_inbox_mails(request): diff --git a/project/views/reference_request_review.py b/project/views/reference_request_review.py index 9329b18..5ff109f 100644 --- a/project/views/reference_request_review.py +++ b/project/views/reference_request_review.py @@ -4,22 +4,24 @@ from flask_security import auth_required from sqlalchemy.exc import SQLAlchemyError from project import app, db -from project.access import access_or_401, has_access, has_admin_unit_member_permission +from project.access import ( + access_or_401, + get_admin_unit_members_with_permission, + has_access, +) from project.dateutils import get_today from project.forms.reference_request import ReferenceRequestReviewForm from project.models import ( - AdminUnitMember, EventDate, EventReferenceRequest, EventReferenceRequestReviewStatus, - User, ) from project.services.admin_unit import ( get_admin_unit_relation, upsert_admin_unit_relation, ) from project.services.reference import create_event_reference_for_request -from project.views.utils import flash_errors, handleSqlError, send_mail +from project.views.utils import flash_errors, handleSqlError, send_mails @app.route("/reference_request//review", methods=("GET", "POST")) @@ -123,17 +125,14 @@ def event_reference_request_review_status(id): def send_reference_request_review_status_mails(request): # Benachrichtige alle Mitglieder der AdminUnit, die diesen Request erstellt hatte - members = ( - AdminUnitMember.query.join(User) - .filter(AdminUnitMember.admin_unit_id == request.event.admin_unit_id) - .all() + members = get_admin_unit_members_with_permission( + request.event.admin_unit_id, "reference_request:create" ) + emails = list(map(lambda member: member.user.email, members)) - for member in members: - if has_admin_unit_member_permission(member, "reference_request:create"): - send_mail( - member.user.email, - gettext("Event review status updated"), - "reference_request_review_status_notice", - request=request, - ) + send_mails( + emails, + gettext("Event review status updated"), + "reference_request_review_status_notice", + request=request, + ) diff --git a/project/views/utils.py b/project/views/utils.py index a576e67..a1dbab1 100644 --- a/project/views/utils.py +++ b/project/views/utils.py @@ -121,11 +121,17 @@ def send_mail(recipient, subject, template, **context): def send_mails(recipients, subject, template, **context): + if len(recipients) == 0: # pragma: no cover + return + msg = Message(subject) msg.recipients = recipients msg.body = render_template("email/%s.txt" % template, **context) msg.html = render_template("email/%s.html" % template, **context) + send_mail_message(msg) + +def send_mail_message(msg): if not mail.default_sender: app.logger.info(",".join(msg.recipients)) app.logger.info(msg.subject) diff --git a/project/views/widget.py b/project/views/widget.py index 6bb19c3..0ab225f 100644 --- a/project/views/widget.py +++ b/project/views/widget.py @@ -10,20 +10,13 @@ from project import app, db from project.access import ( admin_unit_suggestions_enabled_or_404, can_read_event_or_401, - has_admin_unit_member_permission, + get_admin_unit_members_with_permission, ) from project.dateutils import get_next_full_hour from project.forms.event_date import FindEventDateForm from project.forms.event_suggestion import CreateEventSuggestionForm from project.jsonld import DateTimeEncoder, get_sd_for_event_date -from project.models import ( - AdminUnit, - AdminUnitMember, - EventOrganizer, - EventReviewStatus, - EventSuggestion, - User, -) +from project.models import AdminUnit, EventOrganizer, EventReviewStatus, EventSuggestion from project.services.event import ( get_event_date_with_details_or_404, get_event_dates_query, @@ -40,7 +33,7 @@ from project.views.utils import ( get_pagination_urls, get_share_links, handleSqlError, - send_mail, + send_mails, ) @@ -211,17 +204,12 @@ def get_styles(admin_unit): def send_event_inbox_mails(admin_unit, event_suggestion): - members = ( - AdminUnitMember.query.join(User) - .filter(AdminUnitMember.admin_unit_id == admin_unit.id) - .all() - ) + members = get_admin_unit_members_with_permission(admin_unit.id, "event:verify") + emails = list(map(lambda member: member.user.email, members)) - for member in members: - if has_admin_unit_member_permission(member, "event:verify"): - send_mail( - member.user.email, - gettext("New event review"), - "review_notice", - event_suggestion=event_suggestion, - ) + send_mails( + emails, + gettext("New event review"), + "review_notice", + event_suggestion=event_suggestion, + ) diff --git a/tests/api/test_event.py b/tests/api/test_event.py index baa6d88..51a2933 100644 --- a/tests/api/test_event.py +++ b/tests/api/test_event.py @@ -528,3 +528,31 @@ def test_delete(client, seeder, utils, app): event = Event.query.get(event_id) assert event is None + + +def test_report_mail(client, seeder, utils, app, mocker): + user_id, admin_unit_id = seeder.setup_base(admin=False, log_in=False) + event_id = seeder.create_event(admin_unit_id) + seeder.create_user(email="admin@test.de", admin=True) + + mail_mock = utils.mock_send_mails(mocker) + url = utils.get_url("api_v1_event_reports", id=event_id) + response = utils.post_json( + url, + { + "contact_name": "Firstname Lastname", + "contact_email": "firstname.lastname@test.de", + "message": "Diese Veranstaltung wird nicht stattfinden.", + }, + ) + + utils.assert_response_no_content(response) + utils.assert_send_mail_called( + mail_mock, + ["test@test.de", "admin@test.de"], + [ + "Firstname Lastname", + "firstname.lastname@test.de", + "Diese Veranstaltung wird nicht stattfinden.", + ], + ) diff --git a/tests/utils.py b/tests/utils.py index 1a00116..d717ac3 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -175,12 +175,28 @@ class UtilActions(object): ) def mock_send_mails(self, mocker): - return mocker.patch("project.views.utils.send_mails") + return mocker.patch("project.views.utils.send_mail_message") - def assert_send_mail_called(self, mock, recipient): + def assert_send_mail_called(self, mock, recipients, contents=None): mock.assert_called_once() args, kwargs = mock.call_args - assert args[0] == [recipient] + message = args[0] + + if not isinstance(recipients, list): + recipients = [recipients] + + for recipient in recipients: + assert recipient in message.recipients, f"{recipient} not in recipients" + + if contents: + if not isinstance(contents, list): + contents = [contents] + + for content in contents: + assert content in message.body, f"{content} not in body" + assert content in message.html, f"{content} not in html" + + return message def mock_now(self, mocker, year, month, day): from project.dateutils import create_berlin_date diff --git a/tests/views/test_event.py b/tests/views/test_event.py index 9fcf209..b933c45 100644 --- a/tests/views/test_event.py +++ b/tests/views/test_event.py @@ -481,3 +481,11 @@ def test_rrule(client, seeder, utils, app): occurence = json["occurrences"][0] assert occurence["date"] == "20201125T000000" assert occurence["formattedDate"] == '"25.11.2020"' + + +def test_report(seeder, utils): + user_id, admin_unit_id = seeder.setup_base() + event_id = seeder.create_event(admin_unit_id) + + url = utils.get_url("event_report", event_id=event_id) + utils.get_ok(url)