From 1acadbfc6f431f7b2a0563cf09aea5b2bcda927d Mon Sep 17 00:00:00 2001 From: Sergey Kotlov Date: Sat, 25 Apr 2015 13:34:38 +0200 Subject: [PATCH 1/3] Adds notifications if a number of total hours is much less than a number of hours per day multiplied to a number of days --- app/assets/event/form.js | 44 +++ app/controllers/Events.scala | 181 +++++++----- app/controllers/Security.scala | 32 +-- app/controllers/api/LicensesApi.scala | 2 +- app/models/Brand.scala | 2 +- app/models/DynamicResourceChecker.scala | 100 +++++++ app/models/Event.scala | 53 ++-- app/models/EventInvoice.scala | 12 +- app/models/License.scala | 16 -- app/models/service/EventService.scala | 58 +++- app/models/service/LicenseService.scala | 37 ++- app/views/event/details.scala.html | 10 +- app/views/event/form.scala.html | 9 +- conf/messages | 3 +- test/controllers/unit/EventsSpec.scala | 142 ++++++++++ .../unit/TellerResourceHandlerSpec.scala | 190 +++---------- test/helpers/EventHelper.scala | 3 +- .../models/integration/EventServiceSpec.scala | 2 +- .../unit/DynamicResourceCheckerSpec.scala | 268 ++++++++++++++++++ test/models/unit/EventSpec.scala | 30 +- 20 files changed, 863 insertions(+), 331 deletions(-) create mode 100644 app/models/DynamicResourceChecker.scala create mode 100644 test/controllers/unit/EventsSpec.scala create mode 100644 test/models/unit/DynamicResourceCheckerSpec.scala diff --git a/app/assets/event/form.js b/app/assets/event/form.js index fc9ebbd6..e9ae660a 100644 --- a/app/assets/event/form.js +++ b/app/assets/event/form.js @@ -111,6 +111,41 @@ function updateInvoicingOrganisations(organisations, selectedId) { } } +/** + * Calculates number of total hours for event based on its number of days and + * hours per day + */ +function calculateTotalHours() { + var end = $('#schedule_end').data("DateTimePicker").getDate(); + var start = $('#schedule_start').data('DateTimePicker').getDate(); + var days = end.diff(start, 'days') + 1; + return $('#schedule_hoursPerDay').val() * days; +} +/** + * Updates number of total hours for event based on its number + * of days and hours per day + */ +function updateTotalHours() { + var totalHours = calculateTotalHours(); + $('#schedule_totalHours').val(totalHours); +} + +/** + * Checks if the entered number of total hours is move than minimum threshold + * and shows an alert + * @param hours Number of entered total hours + */ +function checkTotalHours(hours) { + var idealHours = calculateTotalHours(); + var threshold = 0.2; + var difference = (idealHours - hours) / parseFloat(idealHours); + if (difference > threshold) { + $('#totalHours-alert').show(); + } else { + $('#totalHours-alert').hide(); + } +} + $(document).ready( function() { /** @@ -258,9 +293,17 @@ $(document).ready( function() { }); $("#schedule_start").on("dp.change", function (e) { $('#schedule_end').data("DateTimePicker").setMinDate(e.date); + updateTotalHours(); + }); + $("#schedule_end").on("dp.change", function(e) { + updateTotalHours(); + }); + $('#schedule_totalHours').on('change', function(e) { + checkTotalHours($(this).val()); }); var brandId = $('#brandId').find(':selected').val(); getEventTypes(brandId, $('#currentEventTypeId').attr('value')); + checkTotalHours($('#schedule_totalHours').val()); facilitators.initialize(brandId); if ($("#emptyForm").attr("value") == 'true') { $("#schedule_start").on("dp.change", function (e) { @@ -275,6 +318,7 @@ $(document).ready( function() { $("#title").on('keyup', function() { $("#eventTypeId").unbind('change'); }); + updateTotalHours(8); } if ($("#confirmed").attr("checked") != "checked") { $("#confirmed-alert").hide(); diff --git a/app/controllers/Events.scala b/app/controllers/Events.scala index c8487d72..dc6c948e 100644 --- a/app/controllers/Events.scala +++ b/app/controllers/Events.scala @@ -30,7 +30,7 @@ import models.UserRole.Role._ import models.brand.EventType import models.event.Comparator import models.event.Comparator.FieldChange -import models.service.{ EventService, Services } +import models.service.Services import models.{ Location, Schedule, _ } import org.joda.time.{ DateTime, LocalDate } import play.api.data.Forms._ @@ -38,11 +38,13 @@ import play.api.data._ import play.api.data.format.Formatter import play.api.i18n.Messages import play.api.libs.json._ +import play.api.libs.ws.WS import play.api.mvc._ +import scala.util.Try import services.integrations.Integrations import views.Countries -object Events extends Controller +trait Events extends Controller with Security with Services with Integrations { @@ -74,37 +76,6 @@ object Events extends Controller } } - val eventTypeFormatter = new Formatter[Long] { - - def bind(key: String, data: Map[String, String]): Either[Seq[FormError], Long] = { - // "data" lets you access all form data values - try { - val eventTypeId = data.get("eventTypeId").get.toLong - try { - val brandId = data.get("brandId").get.toLong - if (EventType.exists(eventTypeId)) { - val event = eventTypeService.find(eventTypeId).get - if (event.brandId == brandId) { - Right(eventTypeId) - } else { - Left(List(FormError("eventTypeId", "Selected event type doesn't belong to a selected brand"))) - } - } else { - Left(List(FormError("eventTypeId", "Unknown event type"))) - } - } catch { - case e: IllegalArgumentException ⇒ Left(List(FormError("brandCode", "Select a brand"))) - } - } catch { - case e: IllegalArgumentException ⇒ Left(List(FormError("eventTypeId", "Select an event type"))) - } - } - - def unbind(key: String, value: Long): Map[String, String] = { - Map(key -> value.toString) - } - } - /** * HTML form mapping for an event’s invoice. */ @@ -124,9 +95,8 @@ object Events extends Controller */ def eventForm(implicit user: UserIdentity) = Form(mapping( "id" -> ignored(Option.empty[Long]), - "eventTypeId" -> of(eventTypeFormatter), - "brandId" -> longNumber(min = 1).verifying( - "error.brand.invalid", (brandId: Long) ⇒ Brand.canManage(brandId, user.account)), + "eventTypeId" -> longNumber(min = 1), + "brandId" -> longNumber(min = 1), "title" -> nonEmptyText(1, 254), "language" -> mapping( "spoken" -> language, @@ -154,7 +124,7 @@ object Events extends Controller "updated" -> ignored(DateTime.now()), "updatedBy" -> ignored(user.fullName), "facilitatorIds" -> list(longNumber).verifying( - "error.event.nofacilitators", (ids: List[Long]) ⇒ ids.nonEmpty))( + Messages("error.event.nofacilitators"), (ids: List[Long]) ⇒ ids.nonEmpty))( { (id, eventTypeId, brandId, title, language, location, details, schedule, notPublic, archived, confirmed, invoice, created, createdBy, updated, updatedBy, facilitatorIds) ⇒ { @@ -217,19 +187,19 @@ object Events extends Controller form.fold( formWithErrors ⇒ formError(user, formWithErrors, None), x ⇒ { - val validLicensees = License.licensees(x.brandId) - if (x.facilitatorIds.forall(id ⇒ { validLicensees.exists(_.id.get == id) })) { - val event = x.insert + validateEvent(x, user.account) map { errors ⇒ + formError(user, + form.withError("facilitatorIds", Messages("error.event.invalidLicense")), + None) + } getOrElse { + val event = eventService.insert(x) val activity = event.activity( user.person, Activity.Predicate.Created).insert - sendEmailNotification(event, - List.empty, - activity) + sendEmailNotification(event, List.empty, activity) Redirect(routes.Events.index()).flashing("success" -> activity.toString) - } else formError(user, - form.withError("facilitatorIds", Messages("error.event.invalidLicense")), - None) + + } }) } @@ -450,32 +420,33 @@ object Events extends Controller val form = eventForm.bindFromRequest form.fold( formWithErrors ⇒ formError(user, formWithErrors, Some(id)), - event ⇒ { - val validLicensees = License.licensees(event.brandId) - if (event.facilitatorIds.forall(id ⇒ { validLicensees.exists(_.id.get == id) })) { + received ⇒ { + validateEvent(received, user.account) map { errors ⇒ + formError(user, + form.withError("facilitatorIds", Messages("error.event.invalidLicense")), + Some(id)) + } getOrElse { val existingEvent = eventService.find(id).get - val updatedEvent = event.copy(id = Some(id)) - updatedEvent.invoice_=(event.invoice.copy(id = existingEvent.invoice.id)) - updatedEvent.facilitatorIds_=(event.facilitatorIds) + val updated = received.copy(id = Some(id)) + updated.invoice_=(received.invoice.copy(id = existingEvent.invoice.id)) + updated.facilitatorIds_=(received.facilitatorIds) // it's important to compare before updating as with lazy // initialization invoice and facilitators data // for an old event will be destroyed - val changes = Comparator.compare(existingEvent, updatedEvent) - updatedEvent.update + val changes = Comparator.compare(existingEvent, updated) + eventService.update(updated) - val activity = updatedEvent.activity( + val activity = updated.activity( user.person, Activity.Predicate.Updated).insert - sendEmailNotification(updatedEvent, + sendEmailNotification(updated, changes, activity) Redirect(routes.Events.details(id)).flashing("success" -> activity.toString) - } else formError(user, - form.withError("facilitatorIds", Messages("error.event.invalidLicense")), - Some(id)) + } }) } @@ -485,16 +456,15 @@ object Events extends Controller */ def confirm(id: Long) = SecuredDynamicAction("event", DynamicRole.Facilitator) { implicit request ⇒ implicit handler ⇒ implicit user ⇒ - eventService.find(id).map { - event ⇒ - val updatedEvent = event.copy(id = Some(id)).copy(confirmed = true) - updatedEvent.invoice_=(event.invoice.copy(id = event.invoice.id)) - updatedEvent.facilitatorIds_=(event.facilitatorIds) - updatedEvent.update - val activity = event.activity( - user.person, - Activity.Predicate.Confirmed).insert - Redirect(routes.Events.details(id)).flashing("success" -> activity.toString) + eventService.find(id).map { event ⇒ + val updated = event.copy(id = Some(id)).copy(confirmed = true) + updated.invoice_=(event.invoice.copy(id = event.invoice.id)) + updated.facilitatorIds_=(event.facilitatorIds) + eventService.update(updated) + val activity = event.activity( + user.person, + Activity.Predicate.Confirmed).insert + Redirect(routes.Events.details(id)).flashing("success" -> activity.toString) }.getOrElse(NotFound) } @@ -542,6 +512,77 @@ object Events extends Controller }.getOrElse(NotFound) } + /** + * Returns none if the given event is valid; otherwise returns a list with errors + * + * @param event Event + * @param account User account + */ + protected def validateEvent(event: Event, account: UserAccount): Option[List[(String, String)]] = { + if (checker(account).isBrandFacilitator(event.brandId)) { + val licenseErrors = validateLicenses(event) map { x ⇒ List(x) } getOrElse List() + val eventTypeErrors = validateEventType(event) map { x ⇒ List(x) } getOrElse List() + val errors = licenseErrors ++ eventTypeErrors + if (errors.isEmpty) + None + else + Some(errors) + } else { + Some(List(("brandId", "error.brand.invalid"))) + } + } + + /** + * Returns error if none of facilitators has a valid license + * + * @param event Event object + */ + protected def validateLicenses(event: Event): Option[(String, String)] = { + val validLicensees = licenseService.licensees(event.brandId) + if (event.facilitatorIds.forall(id ⇒ validLicensees.exists(_.id.get == id))) { + None + } else { + Some(("facilitatorIds", "error.event.invalidLicense")) + } + } + + protected def validateUrls(event: Event): Option[List[(String, String)]] = { + import concurrent.ExecutionContext.Implicits.global + import scala.concurrent.Await + import scala.concurrent.duration._ + import scala.language.postfixOps + event.details.webSite map { x ⇒ + val result = Try(Await.result(WS.url(x).head(), 1 second)).isSuccess + if (result) + None + else + Some(List(("webSite", "error.url.notExist"))) + } getOrElse None + } + + /** + * Returns error if event type doesn't exist or doesn't belong to the brand + * + * @param event Event object + */ + protected def validateEventType(event: Event): Option[(String, String)] = { + val eventType = eventTypeService.find(event.eventTypeId) + eventType map { x ⇒ + if (x.brandId != event.brandId) + Some(("eventTypeId", "error.eventType.wrongBrand")) + else + None + } getOrElse Some(("eventTypeId", "error.eventType.notFound")) + } + + /** + * Returns new resource checker + * + * @param account User account + */ + protected def checker(account: UserAccount): DynamicResourceChecker = + new DynamicResourceChecker(account) + /** * Returns event form with highlighted errors * @param user User object @@ -579,3 +620,5 @@ object Events extends Controller } } } + +object Events extends Events \ No newline at end of file diff --git a/app/controllers/Security.scala b/app/controllers/Security.scala index aa822a6e..a9c5a6b7 100644 --- a/app/controllers/Security.scala +++ b/app/controllers/Security.scala @@ -232,15 +232,11 @@ class TellerResourceHandler(account: Option[UserAccount]) case "add" ⇒ account.editor || account.coordinator case DynamicRole.Coordinator ⇒ id(url) exists { evaluationId ⇒ - account.editor || eventService.findByEvaluation(evaluationId).exists { x ⇒ - brandService.isCoordinator(x.brandId, userId) - } + checker(account).isEvaluationCoordinator(evaluationId) } case DynamicRole.Facilitator ⇒ id(url) exists { evaluationId ⇒ - account.editor || eventService.findByEvaluation(evaluationId).exists { x ⇒ - x.isFacilitator(userId) || brandService.isCoordinator(x.brandId, userId) - } + checker(account).isEvaluationFacilitator(evaluationId) } case _ ⇒ false } @@ -257,17 +253,9 @@ class TellerResourceHandler(account: Option[UserAccount]) meta match { case "add" ⇒ account.editor || account.facilitator || account.coordinator case DynamicRole.Facilitator ⇒ - id(url) exists { eventId ⇒ - account.editor || eventService.find(eventId).exists { x ⇒ - x.isFacilitator(userId) || brandService.isCoordinator(x.brandId, userId) - } - } + id(url) exists { eventId ⇒ checker(account).isEventFacilitator(eventId) } case DynamicRole.Coordinator ⇒ - id(url) exists { eventId ⇒ - account.editor || eventService.find(eventId).exists { x ⇒ - brandService.isCoordinator(x.brandId, userId) - } - } + id(url) exists { eventId ⇒ checker(account).isEventCoordinator(eventId) } case _ ⇒ false } } @@ -281,9 +269,7 @@ class TellerResourceHandler(account: Option[UserAccount]) protected def checkBrandPermission(account: UserAccount, meta: String, url: String): Boolean = { meta match { case DynamicRole.Coordinator ⇒ - id(url) exists { brandId ⇒ - account.editor || brandService.isCoordinator(brandId, account.personId) - } + id(url) exists { brandId ⇒ checker(account).isBrandCoordinator(brandId) } case _ ⇒ false } } @@ -296,4 +282,12 @@ class TellerResourceHandler(account: Option[UserAccount]) try { Some(x.toLong) } catch { case _: NumberFormatException ⇒ None } } + /** + * Returns new resource checker + * + * @param account User account + */ + protected def checker(account: UserAccount): DynamicResourceChecker = + new DynamicResourceChecker(account) + } diff --git a/app/controllers/api/LicensesApi.scala b/app/controllers/api/LicensesApi.scala index bd835ec8..92761eeb 100644 --- a/app/controllers/api/LicensesApi.scala +++ b/app/controllers/api/LicensesApi.scala @@ -77,7 +77,7 @@ trait LicensesApi extends Controller with ApiAuthentication with Services { val date = Try(dateString.map(d ⇒ new LocalDate(d)).getOrElse(LocalDate.now())) date match { case Success(d) ⇒ { - val licensees = License.licensees(x.id.get, d).map(licensee ⇒ LicenseeView(brandCode, licensee)) + val licensees = licenseService.licensees(x.id.get, d).map(licensee ⇒ LicenseeView(brandCode, licensee)) Ok(Json.toJson(licensees)) } case Failure(e) ⇒ BadRequest("Invalid date") diff --git a/app/models/Brand.scala b/app/models/Brand.scala index ddc1a6e0..8853cebd 100644 --- a/app/models/Brand.scala +++ b/app/models/Brand.scala @@ -220,7 +220,7 @@ object Brand { val ord = new Ordering[String] { def compare(x: String, y: String) = collator.compare(x, y) } - License.licensees(brandId, LocalDate.now()).sortBy(_.fullName.toLowerCase)(ord) + LicenseService.get.licensees(brandId, LocalDate.now()).sortBy(_.fullName.toLowerCase)(ord) } def findAllWithCoordinator: List[BrandView] = DB.withSession { implicit session: Session ⇒ diff --git a/app/models/DynamicResourceChecker.scala b/app/models/DynamicResourceChecker.scala new file mode 100644 index 00000000..07310c63 --- /dev/null +++ b/app/models/DynamicResourceChecker.scala @@ -0,0 +1,100 @@ +/* + * Happy Melly Teller + * Copyright (C) 2013 - 2014, Happy Melly http://www.happymelly.com + * + * This file is part of the Happy Melly Teller. + * + * Happy Melly Teller is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Happy Melly Teller is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Happy Melly Teller. If not, see . + * + * If you have questions concerning this license or the applicable additional terms, you may contact + * by email Sergey Kotlov, sergey.kotlov@happymelly.com or + * in writing Happy Melly One, Handelsplein 37, Rotterdam, The Netherlands, 3071 PR + */ + +package models +import models.service.Services + +/** + * This class is responsible for checking dynamic access rights to objects in + * the system + * + * @param user UserAccount + */ +class DynamicResourceChecker(user: UserAccount) extends Services { + + /** + * Returns true if the user is a brand coordinator + * + * @param brandId Brand identifier + */ + def isBrandCoordinator(brandId: Long): Boolean = { + user.editor || brandService.isCoordinator(brandId, user.personId) + } + + /** + * Returns true if the user is a brand facilitator + * + * @param brandId Brand identifier + */ + def isBrandFacilitator(brandId: Long): Boolean = { + user.editor || brandService.isCoordinator(brandId, user.personId) || + licenseService.activeLicense(brandId, user.personId).nonEmpty + } + + /** + * Returns true if the user is an event coordinator + * + * @param eventId Event identifier + */ + def isEventCoordinator(eventId: Long): Boolean = { + user.editor || eventService.find(eventId).exists { x ⇒ + brandService.isCoordinator(x.brandId, user.personId) + } + } + + /** + * Returns true if the user is an event facilitator + * + * @param eventId Event identifier + */ + def isEventFacilitator(eventId: Long): Boolean = { + val userId = user.personId + user.editor || eventService.find(eventId).exists { x ⇒ + x.isFacilitator(userId) || brandService.isCoordinator(x.brandId, userId) + } + } + + /** + * Returns true if the user is an evaluation coordinator + * + * @param evaluationId Evaluation identifier + */ + def isEvaluationCoordinator(evaluationId: Long): Boolean = { + user.editor || eventService.findByEvaluation(evaluationId).exists { x ⇒ + brandService.isCoordinator(x.brandId, user.personId) + } + } + + /** + * Returns true if the user is an evaluation facilitator + * + * @param evaluationId Evaluation identifier + */ + def isEvaluationFacilitator(evaluationId: Long): Boolean = { + val userId = user.personId + user.editor || eventService.findByEvaluation(evaluationId).exists { x ⇒ + x.isFacilitator(userId) || brandService.isCoordinator(x.brandId, userId) + } + } +} \ No newline at end of file diff --git a/app/models/Event.scala b/app/models/Event.scala index d5c6765b..3e011d98 100644 --- a/app/models/Event.scala +++ b/app/models/Event.scala @@ -28,7 +28,7 @@ import akka.actor.{ Actor, Props } import models.database.{ EventFacilitators, Events, Participants } import models.service.{ EventService, Services } import org.joda.money.Money -import org.joda.time.{ DateTime, LocalDate } +import org.joda.time.{ Days, DateTime, LocalDate } import play.api.Play.current import play.api.db.slick.Config.driver.simple._ import play.api.db.slick.DB @@ -45,7 +45,25 @@ import scala.slick.lifted.Query * - how many hours per day it takes * - how many total hours it takes */ -case class Schedule(start: LocalDate, end: LocalDate, hoursPerDay: Int, totalHours: Int) +case class Schedule(start: LocalDate, + end: LocalDate, + hoursPerDay: Int, + totalHours: Int) { + + /** + * Returns true if number of total hours is inside a threshold for an allowed + * number of total hours + */ + def validateTotalHours: Boolean = { + val days = math.abs(Days.daysBetween(start, end).getDays) + 1 + val idealHours = days * hoursPerDay + val difference = (idealHours - totalHours) / idealHours.toFloat + if (difference > 0.2) + false + else + true + } +} /** * Contains optional descriptive data @@ -188,20 +206,6 @@ case class Event( def isFacilitator(personId: Long): Boolean = facilitatorIds.contains(personId) - def insert: Event = DB.withSession { implicit session: Session ⇒ - val insertTuple = (eventTypeId, brandId, title, language.spoken, - language.secondSpoken, language.materials, location.city, - location.countryCode, details.description, details.specialAttention, - details.webSite, details.registrationPage, schedule.start, schedule.end, - schedule.hoursPerDay, schedule.totalHours, notPublic, archived, confirmed, - created, createdBy) - val id = Events.forInsert.insert(insertTuple) - this.facilitatorIds.distinct.foreach(facilitatorId ⇒ - EventFacilitators.insert((id, facilitatorId))) - EventInvoice.insert(this.invoice.copy(eventId = Some(id))) - this.copy(id = Some(id)) - } - /** Deletes this event and its related data from database */ def delete(): Unit = DB.withSession { implicit session: Session ⇒ EventFacilitators.where(_.eventId === this.id.get).mutate(_.delete()) @@ -209,23 +213,6 @@ case class Event( Query(Events).filter(_.id === this.id.get).delete } - def update: Event = DB.withSession { implicit session: Session ⇒ - val updateTuple = (eventTypeId, brandId, title, language.spoken, - language.secondSpoken, language.materials, location.city, - location.countryCode, details.description, details.specialAttention, - details.webSite, details.registrationPage, schedule.start, schedule.end, - schedule.hoursPerDay, schedule.totalHours, notPublic, archived, confirmed, - updated, updatedBy) - val updateQuery = Events.filter(_.id === this.id).map(_.forUpdate) - updateQuery.update(updateTuple) - - EventFacilitators.where(_.eventId === this.id).mutate(_.delete()) - this.facilitatorIds.distinct.foreach(facilitatorId ⇒ - EventFacilitators.insert((this.id.get, facilitatorId))) - EventInvoice.update(this.invoice) - - this - } } object Event { diff --git a/app/models/EventInvoice.scala b/app/models/EventInvoice.scala index 11d6297f..6afc1a80 100644 --- a/app/models/EventInvoice.scala +++ b/app/models/EventInvoice.scala @@ -46,15 +46,15 @@ object EventInvoice { Query(EventInvoices).filter(_.eventId === id).first } - def insert(invoice: EventInvoice): EventInvoice = DB.withSession { implicit session: Session ⇒ - val id = EventInvoices.forInsert.insert(invoice) - invoice.copy(id = Some(id)) - } - def update(invoice: EventInvoice): Unit = DB.withSession { implicit session: Session ⇒ - EventInvoices.filter(_.id === invoice.id).map(_.forUpdate).update((invoice.invoiceTo, invoice.invoiceBy, invoice.number)) + _update(invoice) } + def _update(invoice: EventInvoice)(implicit session: Session): Unit = + EventInvoices.filter(_.id === invoice.id) + .map(_.forUpdate) + .update((invoice.invoiceTo, invoice.invoiceBy, invoice.number)) + def delete(eventId: Long): Unit = DB.withSession { implicit session: Session ⇒ EventInvoices.where(_.eventId === eventId).mutate(_.delete()) } diff --git a/app/models/License.scala b/app/models/License.scala index 03c6dd30..02ea72b0 100644 --- a/app/models/License.scala +++ b/app/models/License.scala @@ -118,22 +118,6 @@ object License { query.firstOption } - /** - * Returns a list of people who are licensed for the given brand on the given - * date, usually today - * - * @param brandId Brand id - * @param date Date of interest - */ - def licensees(brandId: Long, date: LocalDate = LocalDate.now()): List[Person] = DB.withSession { - implicit session: Session ⇒ - val query = for { - license ← Licenses if license.start <= date && license.end >= date && license.brandId === brandId - licensee ← license.licensee - } yield licensee - query.sortBy(_.lastName.toLowerCase).list - } - /** * Returns a list of all people who have ever been licensed for the given brand */ diff --git a/app/models/service/EventService.scala b/app/models/service/EventService.scala index 3f872d14..198979b7 100644 --- a/app/models/service/EventService.scala +++ b/app/models/service/EventService.scala @@ -39,16 +39,6 @@ import scala.slick.lifted.Query class EventService extends Integrations with Services { - /** - * Returns true if a person is a brand manager of this event - * - * @param personId A person unique identifier - */ - def isBrandManager(personId: Long, event: Event): Boolean = DB.withSession { - implicit session: Session ⇒ - brandService.find(event.brandId).exists(_.ownerId == personId) - } - /** * Returns event if it exists, otherwise - None * @@ -164,9 +154,30 @@ class EventService extends Integrations with Services { findByParameters(brandId = None) } + /** + * Adds event and related objects to database + * + * @param event Event object + * @return Updated event object with id + */ + def insert(event: Event): Event = DB.withTransaction { implicit session: Session ⇒ + val insertTuple = (event.eventTypeId, event.brandId, event.title, + event.language.spoken, event.language.secondSpoken, event.language.materials, + event.location.city, event.location.countryCode, event.details.description, + event.details.specialAttention, event.details.webSite, + event.details.registrationPage, event.schedule.start, event.schedule.end, + event.schedule.hoursPerDay, event.schedule.totalHours, event.notPublic, + event.archived, event.confirmed, event.created, event.createdBy) + val id = Events.forInsert.insert(insertTuple) + event.facilitatorIds.distinct.foreach(facilitatorId ⇒ + EventFacilitators.insert((id, facilitatorId))) + EventInvoices.forInsert.insert(event.invoice.copy(eventId = Some(id))) + event.copy(id = Some(id)) + } + /** * Fill events with facilitators (using only one query to database) - * @todo Cover with tests + * TODO: Cover with tests * @param events List of events * @return */ @@ -228,6 +239,31 @@ class EventService extends Integrations with Services { .getOrElse(EventInvoice(None, None, 0, None, None)))) } + /** + * Updates event and related objects in database + * + * @param event Event + * @return Updated event object with id + */ + def update(event: Event): Event = DB.withSession { implicit session: Session ⇒ + val updateTuple = (event.eventTypeId, event.brandId, event.title, + event.language.spoken, event.language.secondSpoken, event.language.materials, + event.location.city, event.location.countryCode, event.details.description, + event.details.specialAttention, event.details.webSite, + event.details.registrationPage, event.schedule.start, event.schedule.end, + event.schedule.hoursPerDay, event.schedule.totalHours, event.notPublic, + event.archived, event.confirmed, event.updated, event.updatedBy) + + val updateQuery = Events.filter(_.id === event.id).map(_.forUpdate) + updateQuery.update(updateTuple) + EventFacilitators.where(_.eventId === event.id).mutate(_.delete()) + event.facilitatorIds.distinct.foreach(facilitatorId ⇒ + EventFacilitators.insert((event.id.get, facilitatorId))) + EventInvoice._update(event.invoice) + + event + } + /** * Updates rating for the given event * @param eventId Event id diff --git a/app/models/service/LicenseService.scala b/app/models/service/LicenseService.scala index d0e275c3..ab48d8c1 100644 --- a/app/models/service/LicenseService.scala +++ b/app/models/service/LicenseService.scala @@ -26,7 +26,7 @@ package models.service import com.github.tototoshi.slick.JodaSupport._ import models.database.{ Licenses, People } -import models.{ Facilitator, LicenseLicenseeView, LicenseView, License } +import models.{ Facilitator, LicenseLicenseeView, LicenseView, License, Person } import org.joda.time.LocalDate import play.api.Play.current import play.api.db.slick.Config.driver.simple._ @@ -64,6 +64,23 @@ class LicenseService extends Services { } } + /** + * Returns active license for the given person and brand if it exists + * + * @TODO add tests + * @param brandId Brand identifier + * @param personId Person identifier + */ + def activeLicense(brandId: Long, personId: Long): Option[License] = DB.withSession { + implicit session: Session ⇒ + Query(Licenses) + .filter(_.licenseeId === personId) + .filter(_.brandId === brandId) + .filter(_.start <= LocalDate.now()) + .filter(_.end >= LocalDate.now()) + .firstOption + } + /** * Returns a list of content licenses for the given person * @param personId Person identifier @@ -80,6 +97,24 @@ class LicenseService extends Services { } } + /** + * Returns a list of people who are licensed for the given brand on the given + * date, usually today + * + * @TODO add tests + * @param brandId Brand id + * @param date Date of interest + */ + def licensees(brandId: Long, date: LocalDate = LocalDate.now()): List[Person] = + DB.withSession { + implicit session: Session ⇒ + val query = for { + license ← Licenses if license.start <= date && license.end >= date && license.brandId === brandId + licensee ← license.licensee + } yield licensee + query.sortBy(_.lastName.toLowerCase).list + } + /** * Returns list of licenses expiring this month for the given brands * diff --git a/app/views/event/details.scala.html b/app/views/event/details.scala.html index e1d0170f..286180cb 100644 --- a/app/views/event/details.scala.html +++ b/app/views/event/details.scala.html @@ -96,8 +96,16 @@

delete it. } + @dynamic(handler, "event", DynamicRole.Coordinator) { + @if(!event.schedule.validateTotalHours) { +
+ Number of total hours is + suspiciosly less than a number of hours per day multiplied to a number of days. +
+ } + } - +
diff --git a/app/views/event/form.scala.html b/app/views/event/form.scala.html index cd352eff..991bb369 100644 --- a/app/views/event/form.scala.html +++ b/app/views/event/form.scala.html @@ -38,7 +38,8 @@

@eventTitle

- @html.select(form("brandId"), brands.map(v => v.id.get.toString -> v.name), '_label -> "Brand") + @html.select(form("brandId"), brands.map(v => v.id.get.toString -> v.name), + '_label -> "Brand", '_help -> "") @html.twoInRow(form("eventTypeId"), html.select(form("eventTypeId"), Seq(("", "Choose an event type")))(asIs, implicitly[Lang]), form("title"), @@ -95,6 +96,12 @@

@eventTitle

html.number(form("schedule.totalHours"))(asIs, implicitly[Lang]), "Hours per Day/Total Hours" ) +
+ Number of total hours + is much less than number of hours per day multiplied to number of days. + Please check if it's a mistake or not. +
+ Misc @html.text(form("details.webSite"), '_label -> "Organizer website", 'placeholder -> "http://", '_help -> "Web site URL") diff --git a/conf/messages b/conf/messages index 43b116ef..eca52955 100644 --- a/conf/messages +++ b/conf/messages @@ -133,7 +133,8 @@ error.event.invalidLicense = Some facilitators do not have valid licenses error.eventType.tooManyEvents = You cannot delete this event type because there are several events of this type error.eventType.nameWrongLength = A name of event type should not be empty or more than 254 characters long event.eventType.nameExists = An event with the given name already exists -event.eventType.notFound = The event type you try to update is not found +event.eventType.notFound = Event type is not found +event.eventType.wrongBrand = Event type doesn't belong to the selected brand error.language.unknown = Unknown language error.membership.tooEarly = Membership date cannot be earlier than 2015-01-01 diff --git a/test/controllers/unit/EventsSpec.scala b/test/controllers/unit/EventsSpec.scala new file mode 100644 index 00000000..d9d5d17a --- /dev/null +++ b/test/controllers/unit/EventsSpec.scala @@ -0,0 +1,142 @@ +/* +* Happy Melly Teller +* Copyright (C) 2013 - 2015, Happy Melly http -> //www.happymelly.com +* +* This file is part of the Happy Melly Teller. +* +* Happy Melly Teller is free software -> you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Happy Melly Teller is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Happy Melly Teller. If not, see //www.gnu.org/licenses/>. +* +* If you have questions concerning this license or the applicable additional +* terms, you may contact by email Sergey Kotlov, sergey.kotlov@happymelly.com +* or in writing Happy Melly One, Handelsplein 37, Rotterdam, +* The Netherlands, 3071 PR +*/ +package controllers.unit + +import controllers.Events +import org.specs2.mutable._ +import org.scalamock.specs2.IsolatedMockFactory +import models.{ Event, UserAccount, UserRole, DynamicResourceChecker } +import models.brand.EventType +import models.service.brand.EventTypeService +import stubs.{ FakeServices, FakeLicenseService } +import helpers.EventHelper +import org.joda.time.LocalDate + +class EventsSpec extends Specification with IsolatedMockFactory { + + class TestEvents extends Events with FakeServices { + def callValidateEvent(event: Event, user: UserAccount): Option[List[(String, String)]] = + validateEvent(event, user) + + def callValidateEventType(event: Event): Option[(String, String)] = + validateEventType(event) + + def callValidateLicenses(event: Event): Option[(String, String)] = + validateLicenses(event) + + def callValidateUrls(event: Event): Option[List[(String, String)]] = + validateUrls(event) + } + + class AnotherTestEvents(checker: DynamicResourceChecker) extends TestEvents { + override def checker(account: UserAccount): DynamicResourceChecker = checker + } + + val controller = new TestEvents + val user = UserAccount(None, 1L, "editor", None, None, None, None) + user.roles_=(List(UserRole.forName("editor"))) + val licenseService = mock[FakeLicenseService] + val eventTypeService = mock[EventTypeService] + controller.licenseService_=(licenseService) + controller.eventTypeService_=(eventTypeService) + + "Event validation should fail" >> { + "if no facilitator has a valid license" in { + (licenseService.licensees _) expects (1L, LocalDate.now()) returning List() + val event = EventHelper.one + val res = controller.callValidateLicenses(event) + res.nonEmpty must_== true + res.get._1 == "facilitatorIds" + res.get._2 == "error.event.invalidLicense" + } + "if user is not allowed to facilitate the brand" in { + class MockedChecker extends DynamicResourceChecker(user) + val checker = mock[MockedChecker] + (checker.isBrandFacilitator _) expects 1L returning false + val anotherController = new AnotherTestEvents(checker) + user.roles_=(List(UserRole.forName("viewer"))) + val event = EventHelper.one + val res = anotherController.callValidateEvent(event, user) + res.nonEmpty must_== true + res.get.exists(_._1 == "brandId") + res.get.exists(_._2 == "error.brand.invalid") + } + "if the event type doesn't belong to the brand" in { + val eventType = EventType(None, 2L, "test", None, 16) + (eventTypeService.find _) expects 1L returning Some(eventType) + val event = EventHelper.one + val res = controller.callValidateEventType(event) + res.nonEmpty must_== true + res.get._1 must_== "eventTypeId" + res.get._2 must_== "error.eventType.wrongBrand" + } + "if the event type doesn't exist" in { + (eventTypeService.find _) expects 1L returning None + val event = EventHelper.one + val res = controller.callValidateEventType(event) + res.nonEmpty must_== true + res.get._1 must_== "eventTypeId" + res.get._2 must_== "error.eventType.notFound" + } + "if website doesn't exist" in { + val details = EventHelper.one.details + val event = EventHelper.one.copy( + details = details.copy(webSite = Some("http://notexsting390812093821.com"))) + val res = controller.callValidateUrls(event) + res.nonEmpty must_== true + res.get.exists(_._1 == "webSite") must_== true + res.get.exists(_._2 == "error.url.notExist") must_== true + } + } + "Event validation should succeed" >> { + "when website exists" in { + val details = EventHelper.one.details + val event = EventHelper.one.copy( + details = details.copy(webSite = Some("http://google.com"))) + val res = controller.callValidateUrls(event) + res.isEmpty must_== true + } + } + "On event validation several errors should be returned" >> { + "when several validations fail" in { + class YetAnotherTestEvent extends TestEvents { + override def validateEventType(event: Event): Option[(String, String)] = + Some(("eventTypeId", "error.eventType.notFound")) + + override def validateLicenses(event: Event): Option[(String, String)] = + Some(("facilitatorIds", "error.event.invalidLicense")) + } + val controller = new YetAnotherTestEvent + val event = EventHelper.one + val res = controller.callValidateEvent(event, user) + res.nonEmpty must_== true + res.get.exists(_._1 == "eventTypeId") must_== true + res.get.exists(_._2 == "error.eventType.notFound") must_== true + res.get.exists(_._1 == "facilitatorIds") must_== true + res.get.exists(_._2 == "error.event.invalidLicense") must_== true + } + } + +} \ No newline at end of file diff --git a/test/controllers/unit/TellerResourceHandlerSpec.scala b/test/controllers/unit/TellerResourceHandlerSpec.scala index 805cbfc6..d78e7ad4 100644 --- a/test/controllers/unit/TellerResourceHandlerSpec.scala +++ b/test/controllers/unit/TellerResourceHandlerSpec.scala @@ -25,17 +25,16 @@ package controllers.unit import controllers.TellerResourceHandler -import helpers.EventHelper import models.UserRole.DynamicRole -import models.service.BrandService -import models.{ UserAccount, UserRole } +import models.{ UserAccount, UserRole, DynamicResourceChecker } import org.scalamock.specs2.MockContext import org.specs2.mutable.Specification -import stubs.{ FakeServices, StubEventService } +import stubs.FakeServices class TellerResourceHandlerSpec extends Specification { - class TestTellerResourceHandler(account: Option[UserAccount]) + class TestTellerResourceHandler(account: Option[UserAccount], + checker: DynamicResourceChecker) extends TellerResourceHandler(account) with FakeServices { def callCheckBrandPermission(account: UserAccount, meta: String, url: String): Boolean = @@ -46,30 +45,26 @@ class TellerResourceHandlerSpec extends Specification { def callCheckEvaluationPermission(account: UserAccount, meta: String, url: String): Boolean = checkEvaluationPermission(account, meta, url) + + override def checker(account: UserAccount): DynamicResourceChecker = checker } - val handler = new TestTellerResourceHandler(None) val editor = UserAccount(None, 1L, "editor", None, None, None, None) editor.roles_=(List(UserRole.forName("editor"))) val viewer = editor.copy(role = "viewer") viewer.roles_=(List(UserRole.forName("viewer"))) + val checker = new DynamicResourceChecker(editor) + val handler = new TestTellerResourceHandler(None, checker) + "When brand permissions are checked for coordinator" >> { - "and user is an Editor then permission should be granted" in { + "isBrandCoordinator function should be called" in new MockContext { + class MockedChecker extends DynamicResourceChecker(editor) + val checker = mock[MockedChecker] + val handler = new TestTellerResourceHandler(None, checker) + (checker.isBrandCoordinator _) expects 1L returning true handler.callCheckBrandPermission(editor, DynamicRole.Coordinator, "/1") must_== true } - "and user is a coordinator then permission should be granted" in new MockContext { - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) - handler.brandService_=(brandService) - handler.callCheckBrandPermission(viewer, DynamicRole.Coordinator, "/1") must_== true - } - "and user is a Viewer then permission should not be granted" in new MockContext { - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) - handler.brandService_=(brandService) - handler.callCheckBrandPermission(viewer, DynamicRole.Coordinator, "/1") must_== false - } "and url doesn't containt brand id then permission should not be granted" in { handler.callCheckBrandPermission(editor, DynamicRole.Coordinator, "/") must_== false } @@ -81,174 +76,61 @@ class TellerResourceHandlerSpec extends Specification { } "When event permissions are checked for coordinator" >> { - "and user is an Editor then permission should be granted" in { - handler.callCheckEventPermission(editor, DynamicRole.Coordinator, "/1") must_== true - } - "and user is a coordinator then permission should be granted" in new MockContext { - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) - val eventService = mock[StubEventService] - (eventService.find(_)).expects(1L).returning(Some(EventHelper.one)) - handler.eventService_=(eventService) - handler.brandService_=(brandService) + "isEventCoordinator function should be called" in new MockContext { + class MockedChecker extends DynamicResourceChecker(editor) + val checker = mock[MockedChecker] + val handler = new TestTellerResourceHandler(None, checker) + (checker.isEventCoordinator _) expects 1L returning true handler.callCheckEventPermission(viewer, DynamicRole.Coordinator, "/1") must_== true } - "and user is a Viewer then permission should not be granted" in new MockContext { - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) - val eventService = mock[StubEventService] - (eventService.find(_)).expects(1L).returning(Some(EventHelper.one)) - handler.eventService_=(eventService) - handler.brandService_=(brandService) - handler.callCheckEventPermission(viewer, DynamicRole.Coordinator, "/1") must_== false - } - "and requested event doesn't not exist then permission should not be granted" in new MockContext { - val eventService = mock[StubEventService] - (eventService.find(_)).expects(1L).returning(None) - handler.eventService_=(eventService) - - handler.callCheckEventPermission(viewer, DynamicRole.Coordinator, "/1") must_== false - } "and url doesn't contain event id then permission should not be granted" in { handler.callCheckEventPermission(editor, DynamicRole.Coordinator, "/") must_== false } } "When event permissions are checked for facilitator" >> { - "and user is an Editor then permission should be granted" in { + "isEventFacilitator function should be called" in new MockContext { + class MockedChecker extends DynamicResourceChecker(editor) + val checker = mock[MockedChecker] + val handler = new TestTellerResourceHandler(None, checker) + (checker.isEventFacilitator _) expects 1L returning true handler.callCheckEventPermission(editor, DynamicRole.Facilitator, "/1") must_== true } - "and user is a coordinator then permission should be granted" in new MockContext { - val event = EventHelper.one - event.facilitatorIds_=(List()) - - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) - val eventService = mock[StubEventService] - (eventService.find(_)).expects(1L).returning(Some(event)) - handler.eventService_=(eventService) - handler.brandService_=(brandService) - handler.callCheckEventPermission(viewer, DynamicRole.Facilitator, "/1") must_== true - } - "and user is a facilitator then permission should be granted" in new MockContext { - val event = EventHelper.one - event.facilitatorIds_=(List(1)) - - val eventService = mock[StubEventService] - (eventService.find(_)).expects(1L).returning(Some(event)) - handler.eventService_=(eventService) - handler.callCheckEventPermission(viewer, DynamicRole.Facilitator, "/1") must_== true - } - "and user is a Viewer then permission should not be granted" in new MockContext { - val event = EventHelper.one - event.facilitatorIds_=(List()) - - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) - val eventService = mock[StubEventService] - (eventService.find(_)).expects(1L).returning(Some(event)) - handler.eventService_=(eventService) - handler.brandService_=(brandService) - handler.callCheckEventPermission(viewer, DynamicRole.Facilitator, "/1") must_== false - } - "and requested event doesn't not exist then permission should not be granted" in new MockContext { - val eventService = mock[StubEventService] - (eventService.find(_)).expects(1L).returning(None) - handler.eventService_=(eventService) - - handler.callCheckEventPermission(viewer, DynamicRole.Facilitator, "/1") must_== false - } "and url doesn't contain event id then permission should not be granted" in { handler.callCheckEventPermission(editor, DynamicRole.Facilitator, "/") must_== false } } - "When brand permissions are checked for random role" >> { + "When event permissions are checked for random role" >> { "then permission should not be granted" in { handler.callCheckEventPermission(editor, "anything", "/1") must_== false } } "When evaluation permissions are checked for coordinator" >> { - "and user is an Editor then permission should be granted" in { + "isEvaluationCoordinator function should be called" in new MockContext { + class MockedChecker extends DynamicResourceChecker(editor) + val checker = mock[MockedChecker] + val handler = new TestTellerResourceHandler(None, checker) + (checker.isEvaluationCoordinator _) expects 1L returning true handler.callCheckEvaluationPermission(editor, DynamicRole.Coordinator, "/1") must_== true } - "and user is a coordinator then permission should be granted" in new MockContext { - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) - val eventService = mock[StubEventService] - (eventService.findByEvaluation(_)).expects(1L).returning(Some(EventHelper.one)) - handler.eventService_=(eventService) - handler.brandService_=(brandService) - handler.callCheckEvaluationPermission(viewer, DynamicRole.Coordinator, "/1") must_== true - } - "and user is a Viewer then permission should not be granted" in new MockContext { - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) - val eventService = mock[StubEventService] - (eventService.findByEvaluation(_)).expects(1L).returning(Some(EventHelper.one)) - handler.eventService_=(eventService) - handler.brandService_=(brandService) - handler.callCheckEvaluationPermission(viewer, DynamicRole.Coordinator, "/1") must_== false - } - "and requested event doesn't not exist then permission should not be granted" in new MockContext { - val eventService = mock[StubEventService] - (eventService.findByEvaluation(_)).expects(1L).returning(None) - handler.eventService_=(eventService) - handler.callCheckEvaluationPermission(viewer, DynamicRole.Coordinator, "/1") must_== false - } "and url doesn't contain event id then permission should not be granted" in { handler.callCheckEvaluationPermission(editor, DynamicRole.Coordinator, "/") must_== false } } "When evaluation permissions are checked for facilitator" >> { - "and user is an Editor then permission should be granted" in { + "isEvaluationFacilitator function should be called" in new MockContext { + class MockedChecker extends DynamicResourceChecker(editor) + val checker = mock[MockedChecker] + val handler = new TestTellerResourceHandler(None, checker) + (checker.isEvaluationFacilitator _) expects 1L returning true handler.callCheckEvaluationPermission(editor, DynamicRole.Facilitator, "/1") must_== true } - "and user is a coordinator then permission should be granted" in new MockContext { - val event = EventHelper.one - event.facilitatorIds_=(List()) - - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) - val eventService = mock[StubEventService] - (eventService.findByEvaluation(_)).expects(1L).returning(Some(event)) - handler.eventService_=(eventService) - handler.brandService_=(brandService) - handler.callCheckEvaluationPermission(viewer, DynamicRole.Facilitator, "/1") must_== true - } - "and user is a facilitator then permission should be granted" in new MockContext { - val event = EventHelper.one - event.facilitatorIds_=(List(1)) - - val eventService = mock[StubEventService] - (eventService.findByEvaluation(_)).expects(1L).returning(Some(event)) - handler.eventService_=(eventService) - handler.callCheckEvaluationPermission(viewer, DynamicRole.Facilitator, "/1") must_== true - } - "and user is a Viewer then permission should not be granted" in new MockContext { - val event = EventHelper.one - event.facilitatorIds_=(List()) - - val brandService = mock[BrandService] - (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) - val eventService = mock[StubEventService] - (eventService.findByEvaluation(_)).expects(1L).returning(Some(event)) - handler.eventService_=(eventService) - handler.brandService_=(brandService) - handler.callCheckEvaluationPermission(viewer, DynamicRole.Facilitator, "/1") must_== false - } - "and requested event doesn't not exist then permission should not be granted" in new MockContext { - val eventService = mock[StubEventService] - (eventService.findByEvaluation(_)).expects(1L).returning(None) - handler.eventService_=(eventService) - - handler.callCheckEvaluationPermission(viewer, DynamicRole.Facilitator, "/1") must_== false - } "and url doesn't contain event id then permission should not be granted" in { handler.callCheckEvaluationPermission(editor, DynamicRole.Facilitator, "/") must_== false } } - "When brand permissions are checked for random role" >> { + "When evaluation permissions are checked for random role" >> { "then permission should not be granted" in { handler.callCheckEvaluationPermission(editor, "anything", "/1") must_== false } diff --git a/test/helpers/EventHelper.scala b/test/helpers/EventHelper.scala index e23275a1..3bd77ab7 100644 --- a/test/helpers/EventHelper.scala +++ b/test/helpers/EventHelper.scala @@ -25,6 +25,7 @@ package helpers import models._ import org.joda.time.{ DateTime, LocalDate } +import models.service.EventService import play.api.libs.json.{ Json, JsValue } object EventHelper { @@ -85,7 +86,7 @@ object EventHelper { country = Some(code), eventTypeId = Some(eventType), facilitatorIds = Some(facilitators)) - event.insert + EventService.get.insert(event) } } } diff --git a/test/models/integration/EventServiceSpec.scala b/test/models/integration/EventServiceSpec.scala index dd964a36..9b81bf90 100644 --- a/test/models/integration/EventServiceSpec.scala +++ b/test/models/integration/EventServiceSpec.scala @@ -197,7 +197,7 @@ class EventServiceSpec extends PlayAppSpec { "Method updateRating" should { "set new rating to 6.5" in { - val event = EventHelper.one.insert + val event = EventService.get.insert(EventHelper.one) event.rating must_== 0.0f EventService.get.updateRating(event.id.get, 6.5f) EventService.get.find(event.id.get) map { x ⇒ diff --git a/test/models/unit/DynamicResourceCheckerSpec.scala b/test/models/unit/DynamicResourceCheckerSpec.scala new file mode 100644 index 00000000..a29b7723 --- /dev/null +++ b/test/models/unit/DynamicResourceCheckerSpec.scala @@ -0,0 +1,268 @@ +/* + * Happy Melly Teller + * Copyright (C) 2013 - 2015, Happy Melly http://www.happymelly.com + * + * This file is part of the Happy Melly Teller. + * + * Happy Melly Teller is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Happy Melly Teller is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Happy Melly Teller. If not, see . + * + * If you have questions concerning this license or the applicable additional terms, you may contact + * by email Sergey Kotlov, sergey.kotlov@happymelly.com or + * in writing Happy Melly One, Handelsplein 37, Rotterdam, The Netherlands, 3071 PR + */ + +package models + +import models.service.BrandService +import org.joda.money.Money +import org.joda.time.LocalDate +import org.specs2.mutable.Specification +import org.scalamock.specs2.MockContext +import stubs.{ FakeServices, StubEventService, FakeLicenseService } +import helpers.EventHelper + +class DynamicResourceCheckerSpec extends Specification { + + class TestDynamicResourceChecker(user: UserAccount) extends DynamicResourceChecker(user) + with FakeServices {} + + val editor = UserAccount(None, 1L, "editor", None, None, None, None) + editor.roles_=(List(UserRole.forName("editor"))) + val viewer = editor.copy(role = "viewer") + viewer.roles_=(List(UserRole.forName("viewer"))) + + "When brand permissions are checked for coordinator" >> { + "and user is an Editor then permission should be granted" in { + val checker = new TestDynamicResourceChecker(editor) + checker.isBrandCoordinator(1L) must_== true + } + "and user is a coordinator then permission should be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) + val checker = new TestDynamicResourceChecker(viewer) + checker.brandService_=(brandService) + checker.isBrandCoordinator(1L) must_== true + } + "and user is a Viewer then permission should not be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) + val checker = new TestDynamicResourceChecker(viewer) + checker.brandService_=(brandService) + checker.isBrandCoordinator(1L) must_== false + } + } + "When brand permissions are checked for facilitator" >> { + "and user is an Editor then permission should be granted" in { + val checker = new TestDynamicResourceChecker(editor) + checker.isBrandFacilitator(1L) must_== true + } + "and user is a coordinator then permission should be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) + val checker = new TestDynamicResourceChecker(viewer) + checker.brandService_=(brandService) + checker.isBrandFacilitator(1L) must_== true + } + "and user is a facilitator then permission should be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) + val licenseService = mock[FakeLicenseService] + val license = License(None, 1L, 1L, "1", LocalDate.now(), + LocalDate.now(), LocalDate.now(), true, Money.parse("EUR 100"), + Some(Money.parse("EUR 100"))) + (licenseService.activeLicense _) expects (1L, 1L) returning Some(license) + val checker = new TestDynamicResourceChecker(viewer) + checker.licenseService_=(licenseService) + checker.brandService_=(brandService) + checker.isBrandFacilitator(1L) must_== true + } + "and user is a Viewer then permission should not be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) + val licenseService = mock[FakeLicenseService] + (licenseService.activeLicense _) expects (1L, 1L) returning None + val checker = new TestDynamicResourceChecker(viewer) + checker.licenseService_=(licenseService) + checker.brandService_=(brandService) + checker.isBrandFacilitator(1L) must_== false + } + } + + "When event permissions are checked for coordinator" >> { + "and user is an Editor then permission should be granted" in { + val checker = new TestDynamicResourceChecker(editor) + checker.isEventCoordinator(1L) must_== true + } + "and user is a coordinator then permission should be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) + val eventService = mock[StubEventService] + (eventService.find(_)).expects(1L).returning(Some(EventHelper.one)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.brandService_=(brandService) + checker.isEventCoordinator(1L) must_== true + } + "and user is a Viewer then permission should not be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) + val eventService = mock[StubEventService] + (eventService.find(_)).expects(1L).returning(Some(EventHelper.one)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.brandService_=(brandService) + checker.isEventCoordinator(1L) must_== false + } + "and requested event doesn't not exist then permission should not be granted" in new MockContext { + val eventService = mock[StubEventService] + (eventService.find(_)).expects(1L).returning(None) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.isEventCoordinator(1L) must_== false + } + } + "When event permissions are checked for facilitator" >> { + "and user is an Editor then permission should be granted" in { + val checker = new TestDynamicResourceChecker(editor) + checker.isEventFacilitator(1L) must_== true + } + "and user is a coordinator then permission should be granted" in new MockContext { + val event = EventHelper.one + event.facilitatorIds_=(List()) + + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) + val eventService = mock[StubEventService] + (eventService.find(_)).expects(1L).returning(Some(event)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.brandService_=(brandService) + checker.isEventFacilitator(1L) must_== true + } + "and user is a facilitator then permission should be granted" in new MockContext { + val event = EventHelper.one + event.facilitatorIds_=(List(1)) + + val eventService = mock[StubEventService] + (eventService.find(_)).expects(1L).returning(Some(event)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.isEventFacilitator(1L) must_== true + } + "and user is a Viewer then permission should not be granted" in new MockContext { + val event = EventHelper.one + event.facilitatorIds_=(List()) + + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) + val eventService = mock[StubEventService] + (eventService.find(_)).expects(1L).returning(Some(event)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.brandService_=(brandService) + checker.isEventFacilitator(1L) must_== false + } + "and requested event doesn't not exist then permission should not be granted" in new MockContext { + val eventService = mock[StubEventService] + (eventService.find(_)).expects(1L).returning(None) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.isEventFacilitator(1L) must_== false + } + } + + "When evaluation permissions are checked for coordinator" >> { + "and user is an Editor then permission should be granted" in { + val checker = new TestDynamicResourceChecker(editor) + checker.isEvaluationCoordinator(1L) must_== true + } + "and user is a coordinator then permission should be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) + val eventService = mock[StubEventService] + (eventService.findByEvaluation(_)).expects(1L).returning(Some(EventHelper.one)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.brandService_=(brandService) + checker.isEvaluationCoordinator(1L) must_== true + } + "and user is a Viewer then permission should not be granted" in new MockContext { + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) + val eventService = mock[StubEventService] + (eventService.findByEvaluation(_)).expects(1L).returning(Some(EventHelper.one)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.brandService_=(brandService) + checker.isEvaluationCoordinator(1L) must_== false + } + "and requested event doesn't not exist then permission should not be granted" in new MockContext { + val eventService = mock[StubEventService] + (eventService.findByEvaluation(_)).expects(1L).returning(None) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.isEvaluationCoordinator(1L) must_== false + } + } + + "When evaluation permissions are checked for facilitator" >> { + "and user is an Editor then permission should be granted" in { + val checker = new TestDynamicResourceChecker(editor) + checker.isEvaluationFacilitator(1L) must_== true + } + "and user is a coordinator then permission should be granted" in new MockContext { + val event = EventHelper.one + event.facilitatorIds_=(List()) + + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(true) + val eventService = mock[StubEventService] + (eventService.findByEvaluation(_)).expects(1L).returning(Some(event)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.brandService_=(brandService) + checker.isEvaluationFacilitator(1L) must_== true + } + "and user is a facilitator then permission should be granted" in new MockContext { + val event = EventHelper.one + event.facilitatorIds_=(List(1)) + + val eventService = mock[StubEventService] + (eventService.findByEvaluation(_)).expects(1L).returning(Some(event)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.isEvaluationFacilitator(1L) must_== true + } + "and user is a Viewer then permission should not be granted" in new MockContext { + val event = EventHelper.one + event.facilitatorIds_=(List()) + + val brandService = mock[BrandService] + (brandService.isCoordinator(_, _)).expects(1L, 1L).returning(false) + val eventService = mock[StubEventService] + (eventService.findByEvaluation(_)).expects(1L).returning(Some(event)) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.brandService_=(brandService) + checker.isEvaluationFacilitator(1L) must_== false + } + "and requested event doesn't not exist then permission should not be granted" in new MockContext { + val eventService = mock[StubEventService] + (eventService.findByEvaluation(_)).expects(1L).returning(None) + val checker = new TestDynamicResourceChecker(viewer) + checker.eventService_=(eventService) + checker.isEvaluationFacilitator(1L) must_== false + } + } +} \ No newline at end of file diff --git a/test/models/unit/EventSpec.scala b/test/models/unit/EventSpec.scala index f061d1e5..ee8860aa 100644 --- a/test/models/unit/EventSpec.scala +++ b/test/models/unit/EventSpec.scala @@ -24,10 +24,9 @@ */ package models.unit -import models.Activity +import models.{ Schedule, Activity, Event } import helpers.{ BrandHelper, EventHelper, PersonHelper } import integration.PlayAppSpec -import models.Event import models.brand.EventType import org.joda.money.Money import org.joda.time.LocalDate @@ -36,18 +35,6 @@ import org.specs2.execute._ class EventSpec extends PlayAppSpec { - override def setupDb() { - PersonHelper.one().insert - PersonHelper.two().insert - PersonHelper.make(Some(4L), "Four", "Tester").insert - PersonHelper.make(Some(5L), "Four", "Tester").insert - BrandHelper.one.insert - (new EventType(None, 1L, "Type 1", None, 16)).insert - (new EventType(None, 1L, "Type 2", None, 16)).insert - EventHelper.addEvents(1L) - EventHelper.addEvents(2L) - } - lazy val event = EventHelper.make( title = Some("Daily Workshop"), city = Some("spb"), @@ -138,6 +125,19 @@ class EventSpec extends PlayAppSpec { f.getAmount.longValue must_== 160L } getOrElse ko } - + } + "Total hours should be invalid if they are less than hoursPerDay * numOfDays * 0.8" >> { + "when an event is two-days long" in { + val schedule = Schedule(LocalDate.now(), LocalDate.now().plusDays(1), 8, 12) + schedule.validateTotalHours must_== false + schedule.copy(totalHours = 1).validateTotalHours must_== false + schedule.copy(totalHours = 13).validateTotalHours must_== true + } + "when an event is one-day long" in { + val schedule = Schedule(LocalDate.now(), LocalDate.now(), 8, 6) + schedule.validateTotalHours must_== false + schedule.copy(totalHours = 3).validateTotalHours must_== false + schedule.copy(totalHours = 7).validateTotalHours must_== true + } } } From 966ce00181b43cd51ac2e342711d7f5bad4894ab Mon Sep 17 00:00:00 2001 From: Sergey Kotlov Date: Sun, 26 Apr 2015 20:53:39 +0200 Subject: [PATCH 2/3] Adds url validity checks to event form --- .gitignore | 3 +- app/assets/event/form.js | 46 +++++++++++++++- app/controllers/Events.scala | 16 ------ app/controllers/Urls.scala | 50 +++++++++++++++++ app/views/event/form.scala.html | 4 +- conf/routes | 1 + test/controllers/acceptance/UrlsSpec.scala | 63 ++++++++++++++++++++++ test/controllers/unit/EventsSpec.scala | 21 -------- 8 files changed, 164 insertions(+), 40 deletions(-) create mode 100644 app/controllers/Urls.scala create mode 100644 test/controllers/acceptance/UrlsSpec.scala diff --git a/.gitignore b/.gitignore index 009601ff..1a82a882 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ dist run-server.sh .tags* .ensime -.ensime_lucene/ \ No newline at end of file +.ensime_lucene/ +TODO \ No newline at end of file diff --git a/app/assets/event/form.js b/app/assets/event/form.js index e9ae660a..98e0abb6 100644 --- a/app/assets/event/form.js +++ b/app/assets/event/form.js @@ -146,6 +146,40 @@ function checkTotalHours(hours) { } } +/** + * Checks if the given url points to an existing page and notifies a user about + * the results of the check + * + * @param url {string} The url of interest + * @param element {string} jQuery selector + */ +function checkUrl(url, element) { + var field = element + '_field'; + if ($.trim(url).length == 0) { + $(field).removeClass('has-error'); + $(field).removeClass('has-success'); + $(element).siblings('span').each(function() { + $(this).text("Web site URL"); + }); + } else { + var fullUrl = jsRoutes.controllers.Urls.validate(url).url; + $.post(fullUrl, {}, null, "json").done(function(data) { + if (data.result == "invalid") { + $(field).addClass('has-error'); + $(element).siblings('span').each(function() { + $(this).text("URL is not correct"); + }); + } else { + $(field).removeClass('has-error'); + $(field).addClass('has-success'); + $(element).siblings('span').each(function() { + $(this).text("URL is correct"); + }); + } + }); + } +} + $(document).ready( function() { /** @@ -301,9 +335,17 @@ $(document).ready( function() { $('#schedule_totalHours').on('change', function(e) { checkTotalHours($(this).val()); }); + $('#details_webSite').on('change', function(e) { + checkUrl($(this).val(), '#details_webSite'); + }); + $('#details_registrationPage').on('change', function(e) { + var url = $(this).val(); + if (url.substring(0, 4) == "http") { + checkUrl(url, '#details_registrationPage'); + } + }); var brandId = $('#brandId').find(':selected').val(); getEventTypes(brandId, $('#currentEventTypeId').attr('value')); - checkTotalHours($('#schedule_totalHours').val()); facilitators.initialize(brandId); if ($("#emptyForm").attr("value") == 'true') { $("#schedule_start").on("dp.change", function (e) { @@ -320,6 +362,8 @@ $(document).ready( function() { }); updateTotalHours(8); } + checkTotalHours($('#schedule_totalHours').val()); + if ($("#confirmed").attr("checked") != "checked") { $("#confirmed-alert").hide(); } diff --git a/app/controllers/Events.scala b/app/controllers/Events.scala index dc6c948e..822d6417 100644 --- a/app/controllers/Events.scala +++ b/app/controllers/Events.scala @@ -38,9 +38,7 @@ import play.api.data._ import play.api.data.format.Formatter import play.api.i18n.Messages import play.api.libs.json._ -import play.api.libs.ws.WS import play.api.mvc._ -import scala.util.Try import services.integrations.Integrations import views.Countries @@ -546,20 +544,6 @@ trait Events extends Controller } } - protected def validateUrls(event: Event): Option[List[(String, String)]] = { - import concurrent.ExecutionContext.Implicits.global - import scala.concurrent.Await - import scala.concurrent.duration._ - import scala.language.postfixOps - event.details.webSite map { x ⇒ - val result = Try(Await.result(WS.url(x).head(), 1 second)).isSuccess - if (result) - None - else - Some(List(("webSite", "error.url.notExist"))) - } getOrElse None - } - /** * Returns error if event type doesn't exist or doesn't belong to the brand * diff --git a/app/controllers/Urls.scala b/app/controllers/Urls.scala new file mode 100644 index 00000000..04ab7ecd --- /dev/null +++ b/app/controllers/Urls.scala @@ -0,0 +1,50 @@ +/* + * Happy Melly Teller + * Copyright (C) 2013 - 2014, Happy Melly http://www.happymelly.com + * + * This file is part of the Happy Melly Teller. + * + * Happy Melly Teller is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Happy Melly Teller is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Happy Melly Teller. If not, see . + * + * If you have questions concerning this license or the applicable additional terms, you may contact + * by email Sergey Kotlov, sergey.kotlov@happymelly.com or + * in writing Happy Melly One, Handelsplein 37, Rotterdam, The Netherlands, 3071 PR + */ +package controllers + +import concurrent.ExecutionContext.Implicits.global +import models.UserRole.Role._ +import play.api.libs.ws.WS +import play.api.libs.json.Json +import scala.concurrent.Await +import scala.concurrent.duration._ +import scala.language.postfixOps +import scala.util.Try + +object Urls extends JsonController with Security { + + /** + * Validates the given url points to an existing page + * + * @param url Url to check + */ + def validate(url: String) = SecuredRestrictedAction(Viewer) { implicit request ⇒ + implicit handler ⇒ implicit user ⇒ + val result = Try(Await.result(WS.url(url).head(), 1 second)).isSuccess + if (result) + jsonOk(Json.obj("result" -> "valid")) + else + jsonOk(Json.obj("result" -> "invalid")) + } +} \ No newline at end of file diff --git a/app/views/event/form.scala.html b/app/views/event/form.scala.html index 991bb369..614042d7 100644 --- a/app/views/event/form.scala.html +++ b/app/views/event/form.scala.html @@ -133,6 +133,8 @@

@eventTitle

}
- + @helper.javascriptRouter("jsRoutes")( + routes.javascript.Urls.validate + ) } } diff --git a/conf/routes b/conf/routes index 163f770f..b96ccd83 100644 --- a/conf/routes +++ b/conf/routes @@ -201,6 +201,7 @@ GET /translation/:lang/edit con POST /translation/:lang controllers.admin.Translations.update(lang: String) POST /translation/:lang/delete controllers.admin.Translations.delete(lang: String) +POST /url/:url/validate controllers.Urls.validate(url) POST /user controllers.UserAccounts.update diff --git a/test/controllers/acceptance/UrlsSpec.scala b/test/controllers/acceptance/UrlsSpec.scala new file mode 100644 index 00000000..cfc186b9 --- /dev/null +++ b/test/controllers/acceptance/UrlsSpec.scala @@ -0,0 +1,63 @@ +/* + * Happy Melly Teller + * Copyright (C) 2013 - 2014, Happy Melly http://www.happymelly.com + * + * This file is part of the Happy Melly Teller. + * + * Happy Melly Teller is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Happy Melly Teller is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Happy Melly Teller. If not, see . + * + * If you have questions concerning this license or the applicable additional terms, you may contact + * by email Sergey Kotlov, sergey.kotlov@happymelly.com or + * in writing Happy Melly One, Handelsplein 37, Rotterdam, The Netherlands, 3071 PR + */ +package controllers.acceptance + +import controllers.Urls +import integration.PlayAppSpec +import play.api.libs.json.JsObject +import play.api.mvc.SimpleResult +import stubs.FakeUserIdentity + +class UrlsSpec extends PlayAppSpec { + + override def is = s2""" + + When a webpage doesn't exist + 'invalid' should be returned $e1 + + When a webpage exists + 'valid' should be returned $e2 + """ + + def e1 = { + val req = prepareSecuredPostRequest(FakeUserIdentity.editor, "/") + val url = "http://notexisting312312398098dsalfjda.com" + val result = Urls.validate(url).apply(req) + + status(result) must equalTo(OK) + val data = contentAsJson(result).asInstanceOf[JsObject] + (data \ "result").as[String] must_== "invalid" + } + + def e2 = { + val req = prepareSecuredPostRequest(FakeUserIdentity.editor, "/") + val url = "http://google.com" + val result = Urls.validate(url).apply(req) + + status(result) must equalTo(OK) + val data = contentAsJson(result).asInstanceOf[JsObject] + (data \ "result").as[String] must_== "valid" + } + +} \ No newline at end of file diff --git a/test/controllers/unit/EventsSpec.scala b/test/controllers/unit/EventsSpec.scala index d9d5d17a..4baa7c78 100644 --- a/test/controllers/unit/EventsSpec.scala +++ b/test/controllers/unit/EventsSpec.scala @@ -45,9 +45,6 @@ class EventsSpec extends Specification with IsolatedMockFactory { def callValidateLicenses(event: Event): Option[(String, String)] = validateLicenses(event) - - def callValidateUrls(event: Event): Option[List[(String, String)]] = - validateUrls(event) } class AnotherTestEvents(checker: DynamicResourceChecker) extends TestEvents { @@ -100,24 +97,6 @@ class EventsSpec extends Specification with IsolatedMockFactory { res.get._1 must_== "eventTypeId" res.get._2 must_== "error.eventType.notFound" } - "if website doesn't exist" in { - val details = EventHelper.one.details - val event = EventHelper.one.copy( - details = details.copy(webSite = Some("http://notexsting390812093821.com"))) - val res = controller.callValidateUrls(event) - res.nonEmpty must_== true - res.get.exists(_._1 == "webSite") must_== true - res.get.exists(_._2 == "error.url.notExist") must_== true - } - } - "Event validation should succeed" >> { - "when website exists" in { - val details = EventHelper.one.details - val event = EventHelper.one.copy( - details = details.copy(webSite = Some("http://google.com"))) - val res = controller.callValidateUrls(event) - res.isEmpty must_== true - } } "On event validation several errors should be returned" >> { "when several validations fail" in { From ac7d7895c336c887db14313252d20e7601996708 Mon Sep 17 00:00:00 2001 From: Sergey Kotlov Date: Sun, 26 Apr 2015 21:35:49 +0200 Subject: [PATCH 3/3] Changes the logic for 'mailto' check and adds 'reconnection' attribute to test database --- app/assets/event/form.js | 7 ++----- test/integration/PlayAppSpec.scala | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/assets/event/form.js b/app/assets/event/form.js index 98e0abb6..91fdc2b4 100644 --- a/app/assets/event/form.js +++ b/app/assets/event/form.js @@ -155,7 +155,7 @@ function checkTotalHours(hours) { */ function checkUrl(url, element) { var field = element + '_field'; - if ($.trim(url).length == 0) { + if ($.trim(url).length == 0 || (url.substring(0, 6) == "mailto")) { $(field).removeClass('has-error'); $(field).removeClass('has-success'); $(element).siblings('span').each(function() { @@ -339,10 +339,7 @@ $(document).ready( function() { checkUrl($(this).val(), '#details_webSite'); }); $('#details_registrationPage').on('change', function(e) { - var url = $(this).val(); - if (url.substring(0, 4) == "http") { - checkUrl(url, '#details_registrationPage'); - } + checkUrl($(this).val(), '#details_registrationPage'); }); var brandId = $('#brandId').find(':selected').val(); getEventTypes(brandId, $('#currentEventTypeId').attr('value')); diff --git a/test/integration/PlayAppSpec.scala b/test/integration/PlayAppSpec.scala index a70a8c0a..0b75112d 100644 --- a/test/integration/PlayAppSpec.scala +++ b/test/integration/PlayAppSpec.scala @@ -42,7 +42,7 @@ trait PlayAppSpec extends PlaySpecification with BeforeAllAfterAll { sequential lazy val app: FakeApplication = { val conf = Map( - "db.default.url" -> "jdbc:mysql://localhost/teller_test", + "db.default.url" -> "jdbc:mysql://localhost/teller_test?reconnect=true&characterEncoding=UTF-8", "db.default.user" -> "root", "db.default.password" -> "", "logger.play" -> "ERROR",