diff --git a/VERSIONS_HOTLINE/57845 b/VERSIONS_HOTLINE/57845
new file mode 100644
index 0000000000000000000000000000000000000000..5b67ba35bc8d5a202ce0442ab71275e2f579538a
--- /dev/null
+++ b/VERSIONS_HOTLINE/57845
@@ -0,0 +1 @@
+ - ticket #57845 : Réservation par mail : correction d'une erreur lorsqu'aucun message n'est saisi
\ No newline at end of file
diff --git a/application/modules/opac/controllers/RechercheController.php b/application/modules/opac/controllers/RechercheController.php
index 8482bad507bfb65c9a16fa6cae8f21c951726d81..d2136fe84119f3d861cf2b3e2584163c203f2244 100644
--- a/application/modules/opac/controllers/RechercheController.php
+++ b/application/modules/opac/controllers/RechercheController.php
@@ -427,115 +427,64 @@ class RechercheController extends ZendAfi_Controller_Action {
 
 
   public function reservationAction() {
-    if ($this->_request->isPost()) {
-      $id_notice = (int)$this->_request->getPost('id_notice');
-      $id_bib = (int)$this->_request->getPost('id_bib');
-      $mail_bib = $this->_request->getPost('mail_bib');
-      $bib_name = $this->_request->getPost('bib_name');
-      $user_name = $this->_request->getPost('user_name');
-      $demande = $this->_request->getPost('demande');
-      $user_mail = $this->_request->getPost('user_mail');
-      $code_saisi = $this->_request->getPost('code_saisi');
-      $cote = $this->_request->getPost('cote');
-
-      // Test field
-      $Syntaxe='#^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,6}$#';
-      $messages = array();
-      if(trim($user_name) =="")
-        $messages []= $this->view->_("Vous n'avez pas saisi vos Nom et Prénom :");
-      if(strlen($demande) <=1)
-        $messages []= $this->view->_("Vous n'avez pas saisi de demande :");
-      if(!preg_match($Syntaxe,$user_mail))
-        $messages []= $this->view->_("Votre adresse e-mail est incorrecte.");
-      if($code_saisi != $_SESSION["captcha_code"])
-        $messages []= $this->view->_("Le code anti-spam est incorrect.");
-      $errorMessage = implode(',', $messages);
-
-      if($errorMessage=="") {
-        // Stats réservation
-        //$stat=new Class_StatsNotices();
-        //$stat->addStatReservation($id_notice);
-
-        $notice = ($model = Class_Notice::find($id_notice)) ? $model->getNotice('JAE') : [];
-
-        $texte_mail_resa = str_replace('%0D%0A',chr(13).chr(10),getVar('TEXTE_MAIL_RESA'));
-        $texte_mail_resa_ok = urldecode($texte_mail_resa);
-
-        // Envoie de mail
-        $message_user = sprintf("%s\r\n\r\n", utf8_decode($texte_mail_resa_ok));
-        $messages = array();
-        $messages []= $this->view->_("Nom et prénom : %s", $user_name);
-        $messages []= '';
-        $messages []= utf8_decode($this->view->_("Notice réservée : "));
-        $messages []= $this->view->_("Titre : %s", utf8_decode($notice["J"]));
-        $messages []= $this->view->_("Auteur : %s", utf8_decode($notice["A"]));
-        $messages []= $this->view->_("Editeur : %s", utf8_decode($notice["E"]));
-        $messages []= $this->view->_("Cote : %s", utf8_decode($cote));
-        $messages []= '';
-        $messages []= utf8_decode($this->view->_("Message du demandeur :"));
-        $messages []= utf8_decode($demande);
-        $message = implode("\r\n", $messages);
-
-        //pour la bibliothèque
-        $mail = new ZendAfi_Mail('utf8');
-        $mail
-          ->setSubject($this->view->_("Demande de réservation de document"))
-          ->setBodyText($message)
-          ->setFrom($user_mail,
-                    $user_name)
-          ->addTo($mail_bib)
-          ->send();
-
-        //pour l'utilisateur
-        $mail = new ZendAfi_Mail('utf8');
-        $mail
-          ->setSubject($this->view->_("Demande de réservation de document"))
-          ->setBodyText($message_user.$message)
-          ->setFrom('nobody@noreply.fr',
-                    Class_Bib::getLoader()->find($id_bib)->getLibelle())
-          ->addTo($user_mail)
-          ->send();
-
-        $this->_redirect('opac/recherche/viewnotice/id/'.$id_notice."?type_doc=".$notice["type_doc"]);
-      }
-      else
-        {
-          $bib = $class_bib->getBibById($id_bib);
-          $this->view->id_bib = $id_bib;
-          $this->view->nom_bib = $bib->LIBELLE;
-          $this->view->mail_bib = $bib->MAIL;
-          $this->view->id_notice = $id_notice;
-          $this->view->errorMessage = $errorMessage;
-          $this->view->user_name = $user_name;
-          $this->view->demande = $demande;
-          $this->view->id_notice = $id_notice;
-          $this->view->user_mail = $user_mail;
-
-          $resa = getVar("RESA_CONDITION");
-          $resa_condition = str_replace('%0D%0A','<br />',$resa);
-          $this->view->condition_resa = urldecode($resa_condition);
-        }
+    if ((!$library = Class_Bib::find((int)$this->_getParam('id_bib')))
+        || !$record = Class_Notice::find((int)$this->_getParam('id_notice'))) {
+      $this->_redirectToIndex();
+      return;
     }
-    // Entree dans le formulaire
-    else
-    {
-      // Parametres
-      $id_bib = (int)$this->_request->getParam('id_bib');
-      $id_notice = (int)$this->_request->getParam('id_notice');
-      $cote = $this->_request->getParam('cote');
-
-      // Mode mail
-      $bib = Class_Bib::find($id_bib);
-      $this->view->id_bib = $id_bib;
-      $this->view->nom_bib = $bib->LIBELLE;
-      $this->view->mail_bib = $bib->MAIL;
-      $this->view->id_notice = $id_notice;
-      $this->view->cote = $cote;
-
-      $resa = getVar("RESA_CONDITION");
-      $resa_condition = str_replace('%0D%0A','<br />',$resa);
-      $this->view->condition_resa = urldecode($resa_condition);
+
+    $form = new ZendAfi_Form_Hold(['action' => $this->view->url(),
+                                   'data-backurl' => $this->view->url(['action' => 'viewnotice']),
+                                   'library' => $library]);
+
+    if ($this->_request->isPost()
+        && $form->isValid($this->_request->getPost())) {
+
+      $subject = $this->_('Demande de réservation de document');
+
+      //pour la bibliothèque
+      $messages = [];
+      $messages []= $this->_("Nom et prénom : %s", $form->getValue('user_name'));
+      $messages []= '';
+      $messages []= utf8_decode($this->_("Notice réservée : "));
+      $messages []= $this->_("Titre : %s", utf8_decode($record->getTitrePrincipal()));
+      $messages []= $this->_("Auteur : %s", utf8_decode($record->getAuteurPrincipal()));
+      $messages []= $this->_("Editeur : %s", utf8_decode($record->getEditeur()));
+      $messages []= $this->_("Cote : %s", utf8_decode($this->_getParam('cote')));
+      $messages []= '';
+      $messages []= utf8_decode($this->_("Message du demandeur :"));
+      $messages []= utf8_decode($form->getValue('demande'));
+      $message = implode("\r\n", $messages);
+
+      (new ZendAfi_Mail('utf8'))
+        ->setSubject($subject)
+        ->setBodyText($message)
+        ->setFrom($form->getValue('user_mail'), $form->getValue('user_name'))
+        ->addTo($library->getMail())
+        ->send();
+
+
+      //pour l'utilisateur
+      $texte_mail_resa = str_replace('%0D%0A', chr(13).chr(10), getVar('TEXTE_MAIL_RESA'));
+      $texte_mail_resa_ok = urldecode($texte_mail_resa);
+      $message_user = sprintf("%s\r\n\r\n", utf8_decode($texte_mail_resa_ok));
+
+      (new ZendAfi_Mail('utf8'))
+        ->setSubject($subject)
+        ->setBodyText($message_user . $message)
+        ->setFrom('nobody@noreply.fr', $library->getLibelle())
+        ->addTo($form->getValue('user_mail'))
+        ->send();
+
+      $this->_redirect('opac/recherche/viewnotice/id/'.$record->getId()."?type_doc=".$record->getTypeDoc());
+
+      return;
     }
+
+    $resa = getVar("RESA_CONDITION");
+    $resa_condition = str_replace('%0D%0A','<br />',$resa);
+    $this->view->condition_resa = urldecode($resa_condition);
+    $this->view->form = $form;
   }
 
 
diff --git a/application/modules/opac/views/scripts/recherche/reservation.phtml b/application/modules/opac/views/scripts/recherche/reservation.phtml
index 7a3749aa381e990d546e5ef6142d654f2d0d09f6..3da291b619bd8eebdad73f4cc67f18276c30b93b 100644
--- a/application/modules/opac/views/scripts/recherche/reservation.phtml
+++ b/application/modules/opac/views/scripts/recherche/reservation.phtml
@@ -1,54 +1,18 @@
 <div id="resa">
-<form action="<?php echo $this->url(['controller'=>'recherche','action'=>'reservation']); ?>" method="post" id="envoiemail" name="envoiemail" >
-<table width="630" border="0" cellspacing="1" cellpadding="3">
+  <table width="630" border="0" cellspacing="1" cellpadding="3">
     <tr>
       <td colspan="2"><h1><?php echo $this->_("Demande de réservation d'un document du réseau:") ?></h1><br />
-    
-    <?php
+        <?php
         $this->openBoite("Pour réserver un ou des document(s), veuillez remplir le formulaire ci-dessous.");
         echo ($this->condition_resa);
-        $this->closeBoite(); 
-     ?>
+        $this->closeBoite();
+        ?>
         <br /><br /></td>
     </tr>
     <tr>
       <td width="177" valign="top" colspan="2" style="text-align:center;font-weight:bold;"><?php echo $this->errorMessage; ?></td>
-    <tr>
-      <td width="177" align="right" valign="top"><div align="right"><?php echo $this->_('Bibliothèque : ') ?></div></td>
-      <td width="438">
-
-    <input type="text" disabled="disabled" value="<?php echo $this->nom_bib; ?>" style="width:400px;" name="bib_name" id="bib_name">
-    </td>
-    </tr>
-    <tr>
-      <td align="right" valign="top"><div align="right"><?php echo $this->_('Vos Nom et Prénom : ') ?></div></td>
-      <td><input type="text" value="<?php echo $this->user_name; ?>" style="width:400px;" name="user_name" id="user_name">&nbsp;</td>
-    </tr>
-    <tr>
-      <td align="right" valign="top"><div align="right"><?php echo $this->_('Votre message :') ?></div></td>
-      <td><textarea style="width:400px;height:150px;" name="demande" id="demande"><?php echo $this->demande; ?></textarea></td>
-    </tr>
-    <tr>
-      <td align="right" valign="top"><div align="right"><?php echo $this->_('Votre adresse E-mail :') ?></div></td>
-      <td><input type="text" value="<?php echo $this->user_mail; ?>" style="width:400px;" name="user_mail" id="user_mail"></td>
-    </tr>
-   <tr>
-      <td align="right" valign="top"><div align="right"><?php echo $this->_('Code Anti-spam :') ?></div></td>
-      <td><div align="center" style="float:left;width:100px;"><img src='<?php echo $this->url(["controller"=>"auth","action"=>"generatecaptcha"]);?>'  border='0' /></div>
-      <div style="width:404px;text-align:right" align="right"><input type="text" value="" style="width:295px;height:15px;" name="code_saisi" id="code_saisi"></div></td>
-    </tr>
-    <tr>
-      <td colspan="2" align="right" valign="top"><div align="center">
-    <input type="hidden" value="<?php echo $this->id_notice; ?>" name="id_notice" id="id_notice">
-    <input type="hidden" value="<?php echo $this->id_bib; ?>" name="id_bib" id="id_bib">
-    <input type="hidden" value="<?php echo $this->mail_bib; ?>" name="mail_bib" id="mail_bib">
-      <input type="hidden" value="<?php echo $this->cote; ?>" name="cote" id="cote">
-    <br /><input type="submit" value="<?php echo $this->_('Envoyer') ?>"/>
-    &nbsp;-&nbsp;
-    <input type="button" value="<?php echo $this->_('Annuler') ?>" onclick="window.location='<?php echo $this->url(['controller'=>'recherche',
-             'action'=>'viewnotice',
-             'id_notice'=>$this->id_notice]); ?>'"/></div></td>
     </tr>
   </table>
-  </form>
-</div>
\ No newline at end of file
+
+  <?php echo $this->renderForm($this->form);?>
+</div>
diff --git a/library/ZendAfi/Form/Decorator/Simplecaptcha.php b/library/ZendAfi/Form/Decorator/Simplecaptcha.php
new file mode 100644
index 0000000000000000000000000000000000000000..89c8d91c74326d63c2e877de58451059d2783253
--- /dev/null
+++ b/library/ZendAfi/Form/Decorator/Simplecaptcha.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Decorator_Simplecaptcha extends Zend_Form_Decorator_Abstract {
+  public function render($content) {
+    $view = $this->_element->getView();
+
+    return $view->tagImg($view->url(['controller' => 'auth',
+                                     'action' => 'generatecaptcha'],
+                                    null, true),
+                         ['id' => $this->_element->getId() . '_preview'])
+      . $content;
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Form/Element/FixedText.php b/library/ZendAfi/Form/Element/FixedText.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3aa1691003516ba8b1d0fb404e764f65b23c6a9
--- /dev/null
+++ b/library/ZendAfi/Form/Element/FixedText.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Element_FixedText extends Zend_Form_Element_Text{
+  public function init() {
+    $this->setOptions(['disabled' => true]);
+  }
+
+
+  public function isValid($value) {
+    return true;
+  }
+}
diff --git a/library/ZendAfi/Form/Element/Simplecaptcha.php b/library/ZendAfi/Form/Element/Simplecaptcha.php
new file mode 100644
index 0000000000000000000000000000000000000000..da6ed0c45e949ce3f5f222250591329c0bae9922
--- /dev/null
+++ b/library/ZendAfi/Form/Element/Simplecaptcha.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Element_Simplecaptcha extends Zend_Form_Element_Text {
+  public function __construct($spec, $options=null) {
+    parent::__construct($spec, $options);
+
+    $this->setOptions(['required' => true,
+                       'allowEmpty' => false]);
+
+    $decorators = $this->_decorators;
+    $this->_decorators = [];
+
+    foreach ($decorators as $name => $value) {
+      $this->_decorators[$name] = $value;
+      if ('ViewHelper' == $name)
+        $this->_decorators['Simplecaptcha'] = new ZendAfi_Form_Decorator_Simplecaptcha();
+    }
+
+    $this->addValidator(new ZendAfi_Validate_Simplecaptcha());
+  }
+
+
+  public function render(Zend_View_Interface $view = null) {
+    // never keep value in rendering
+    $this->setValue('');
+    return parent::render($view);
+  }
+}
diff --git a/library/ZendAfi/Form/Hold.php b/library/ZendAfi/Form/Hold.php
new file mode 100644
index 0000000000000000000000000000000000000000..940ba5cbecab692ff23f3ac6a5ae2252ecfc1c16
--- /dev/null
+++ b/library/ZendAfi/Form/Hold.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Form_Hold extends ZendAfi_Form {
+  public function init() {
+    parent::init();
+
+    $library = $this->getAttrib('library');
+    $this->removeAttrib('library');
+
+    $this
+      ->setAttrib('name', 'hold_form')
+      ->setMethod(static::METHOD_POST)
+
+      ->addElement('fixedText', 'bib_name',
+                   ['label' => $this->_('Bibliothèque : '),
+                    'value' => $library ? $library->getLibelle() : '',
+                   ])
+      ->addElement('text', 'user_name',
+                   ['label' => $this->_('Vos Nom et Prénom : '),
+                    'required' => true,
+                    'allowEmpty' => false,
+                   ])
+      ->addElement('textarea', 'demande',
+                   ['label' => $this->_('Votre message : '),
+                    'required' => true,
+                    'allowEmpty' => false,
+                   ])
+      ->addElement('email', 'user_mail',
+                   ['label' => $this->_('Votre adresse E-mail : '),
+                    'required' => true,
+                    'allowEmpty' => false,
+                   ])
+      ->addElement('simplecaptcha', 'code_saisi',
+                   ['label' => $this->_('Code Anti-spam : ')])
+
+      ->addDisplayGroup(['bib_name', 'user_name', 'demande', 'user_mail', 'code_saisi'],
+                        'common', [])
+      ;
+  }
+}
diff --git a/library/ZendAfi/Validate/Simplecaptcha.php b/library/ZendAfi/Validate/Simplecaptcha.php
new file mode 100644
index 0000000000000000000000000000000000000000..a0d0af59e7fbb7c5187c9f72f567ba9e9abb76cb
--- /dev/null
+++ b/library/ZendAfi/Validate/Simplecaptcha.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Copyright (c) 2012-2017, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH 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 AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Validate_Simplecaptcha extends Zend_Validate_Abstract {
+  const INVALID_CAPTCHA = 'invalidCaptcha';
+
+  protected $_messageTemplates = [self::INVALID_CAPTCHA => 'Le code anti-spam est incorrect'];
+
+
+  public function isValid($value) {
+    $this->_setValue((string)$value);
+
+    if ($value == $_SESSION['captcha_code'])
+      return true;
+
+    $this->_error(static::INVALID_CAPTCHA);
+    return false;
+  }
+}
\ No newline at end of file
diff --git a/public/opac/css/global.css b/public/opac/css/global.css
index a667119e2ce5aa13ff124f26f773ce02c9cdbc07..a1901e60938fc461420d2ccb321d3b019799b610 100644
--- a/public/opac/css/global.css
+++ b/public/opac/css/global.css
@@ -759,8 +759,13 @@ input {
 
 input[type='password'],
 input[type='text'],
+input[type='email'],
 input[type='url'] {border:1px solid #C8C8C8;}
 
+#hold_form input,
+#hold_form textarea { width:400px; }
+#hold_form textarea { height:150px; }
+#hold_form input[name='code_saisi'] { width:297px; }
 
 .bouton:not(div),
 .bouton:not(div):visited {	
@@ -3379,6 +3384,38 @@ dd.picture {
     width: 69%;
 }
 
+
+div.boutons {
+    margin-top:15px;
+    text-align:center;
+}
+
+div.boutons > div {
+    display:inline-block;
+}
+
+fieldset {
+    border:none;
+}
+
+form td {
+    vertical-align:top;
+}
+
+form td.droite {
+    text-align:right;
+}
+
+form td.gauche {
+    text-align:left;
+}
+
+form td.gauche img {
+    display:inline-block;
+    vertical-align:middle;
+}
+
+
 #opac-dialog .boutons {
     margin-top: 2em;
     text-align: center;
@@ -3408,11 +3445,11 @@ dd.picture {
     background:transparent!important;
 }
 
-div.bouton > * {
+#opac-dialog div.bouton > * {
     visibility: hidden;
 }
 
-div.bouton a tr td:nth-child(2) img {
+#opac-dialog div.bouton a tr td:nth-child(2) img {
     vertical-align: middle;
     visibility: visible;
 }
diff --git a/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php b/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
index 8ad700fb35fee1c9263f450c5d89a45460f0ecf9..f84f21dae803badda27e58e8fb88f59db7028183 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerReservationTest.php
@@ -276,20 +276,18 @@ class RechercheControllerReservationWithMailPostAction extends AbstractControlle
     $mock_transport = new MockMailTransport();
     Zend_Mail::setDefaultTransport($mock_transport);
 
-    $this->fixture('Class_Bib', ['id' => 4, 'libelle' => 'Astrolabe']);
+    $this->fixture('Class_Bib', ['id' => 4,
+                                 'libelle' => 'Astrolabe',
+                                 'mail' => 'zork@gloub.fr',]);
+
     $this->fixture('Class_Notice', ['id' => 4,
                                     'unimarc' => "01570nam0 2200325   450 0010007000000100033000070200017000400210027000571000041000841010008001251020007001331050018001401060006001582000106001642100075002702150044003452250023003893000125004123000020005373000137005573300265006943450018009594100051009775120027010286060033010556060060010886760012011487000045011608010039012052218529  a2-86642-370-4bbr.d8,95 EUR  aFRb00347575  aFRbDLE-20031204-51138  a20031107d2003    m  h0frey0103    ba| afre  aFR  ay   z   000y|  ar1 aCinema d'animationbTexte impriméedessin animé, marionnettes, images de synthèsefBernard Genin  a[Paris]c\"Cahiers du cinéma\"cSCEREN-CNDPdcop. 2003gimpr. en Italie  a95 p.cill., couv. ill. en coul.d19 cm2 aLes petits cahiers  aLa couv. porte en plus : \"du crayon à l'ordinateur, pour ou contre Disney, Europe-Japon : le dessin animé aujourd'hui\"  aBibliogr. p. 93  aSCEREN = Services, cultures, éditions, ressources pour l'éducation nationale. CNDP = Centre national de documentation pédagogique  aPrésente un historique du cinéma d'animation, un survol des différentes productions nationales à travers le monde (Etats-Unis, Japon, France, Canada), les techniques du volume animé, l'image de synthèse, mais aussi l'oeuvre de Disney et le film d'auteur.  b9782866423704 032525826tLes Petits cahiers (Paris)x1633-90531 aLe cinéma d'animation| 31053394aAnimation (cinéma)| 31031625aDessins animés32195497xHistoire et critique  a791.431 |32547161aGeninbBernardf1946-....4070 0aFRbBNFc20031107gAFNOR2intermrc"]);
 
-    $this->postDispatch('/recherche/reservation',
-                        ['id_notice' => 4,
-                         'id_bib' => 4,
-                         'mail_bib' => 'zork@gloub.fr',
-                         'user_name' => 'nanuk',
+    $this->postDispatch('/recherche/reservation/id_notice/4/id_bib/4/cote/XYZ',
+                        ['user_name' => 'nanuk',
                          'demande' => 'je veux le livre',
                          'user_mail' => 'nanuk@gloub.com',
-                         'code_saisi' => '1234',
-                         'cote' => 'XYZ'],
-                        true);
+                         'code_saisi' => '1234']);
 
     $this->_sent_mails = $mock_transport->getSentMails();
     $this->first_mail = array_first($this->_sent_mails);
@@ -488,38 +486,227 @@ class RechercheControllerReservationWithWebServiceKohaTest extends AbstractContr
 
 
 class RechercheControllerReservationWithMailFormTest extends AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
 
   public function setUp() {
     parent::setUp();
 
-    $this->fixture('Class_Bib', ['id' => 88, 'libelle' => 'My Library', 'mail' => 'my@library.com']);
-    Class_Bib::beVolatile();
+    $this->fixture('Class_Bib', ['id' => 88,
+                                 'libelle' => 'My Library',
+                                 'mail' => 'my@library.com']);
+
+    $this->fixture('Class_Notice',
+                   ['id' => 550171,
+                    'type_doc' => 1,
+                    'titre_principal' => 'Test record',
+                    'auteur_principal' => 'Test author',
+                    'editeur' => 'Test editor',
+                   ]);
+
     $this->dispatch('/recherche/reservation/id/550171/id_int_bib/88/id_bib/88/id_notice/550171/cote/P+TRA', true);
   }
 
 
   /** @test */
-  public function bibIdShouldBePresentInForm() {
-    $this->assertXPath('//input[@type="hidden"][@name="id_bib"][@value="88"]');
+  public function bibNameShouldBePresentInForm() {
+    $this->assertXPath('//input[@type="text"][@disabled][@name="bib_name"][@value="My Library"]');
   }
 
 
   /** @test */
-  public function bibMailShouldBePresentInForm() {
-    $this->assertXPath('//input[@type="hidden"][@name="mail_bib"][@value="my@library.com"]', $this->_response->getBody());
+  public function shouldContainsUserName() {
+    $this->assertXPath('//input[@type="text"][@name="user_name"]');
   }
 
 
   /** @test */
-  public function bibNameShouldBePresentInForm() {
-    $this->assertXPath('//input[@type="text"][@name="bib_name"][@value="My Library"]', $this->_response->getBody());
+  public function shouldContainsMessage() {
+    $this->assertXPath('//textarea[@name="demande"]');
+  }
+
+
+  /** @test */
+  public function shouldContainsUserMail() {
+    $this->assertXPath('//input[@type="email"][@name="user_mail"]');
   }
 
 
   /** @test */
-  public function noticeIdShouldBePresentInForm() {
-    $this->assertXPath('//input[@type="hidden"][@name="id_notice"][@value="550171"]');
+  public function shouldContainsCaptcha() {
+    $this->assertXPath('//input[@type="text"][@name="code_saisi"]');
   }
 }
 
-?>
\ No newline at end of file
+
+
+
+abstract class RechercheControllerReservationWithMailFormPostTestCase
+  extends AbstractControllerTestCase {
+  protected
+    $_storm_default_to_volatile = true,
+    $_mail_transport;
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Bib', ['id' => 88,
+                                 'libelle' => 'My Library',
+                                 'mail' => 'my@library.com']);
+
+    $this->fixture('Class_Notice',
+                   ['id' => 550171,
+                    'type_doc' => 1,
+                    'titre_principal' => 'Test record',
+                    'auteur_principal' => 'Test author',
+                    'editeur' => 'Test editor',
+                   ]);
+
+    $_SESSION['captcha_code'] = 'e2df0';
+
+    Zend_Mail::setDefaultTransport($this->_mail_transport = new MockMailTransport());
+  }
+
+
+  protected function postDatas($datas) {
+    $this->postDispatch('/recherche/reservation/id/550171/id_int_bib/88/id_bib/88/id_notice/550171/cote/P+TRA',
+                        $datas);
+  }
+
+
+  protected function assertElementError($path, $message) {
+    $this->assertXPathContentContains($path . '/following-sibling::ul[@class="errors"]/li', $message,
+                                  $this->_response->getBody());
+  }
+}
+
+
+
+class RechercheControllerReservationWithMailFormInvalidPostTest
+  extends RechercheControllerReservationWithMailFormPostTestCase{
+
+
+  /** @test */
+  public function withEmptyPostAllFieldsShouldBeRequired() {
+    $this->postDatas([]);
+
+    foreach([
+             ['//input[@name="user_name"]', 'Une valeur est requise'],
+             ['//textarea[@name="demande"]', 'Une valeur est requise'],
+             ['//input[@name="user_mail"]', 'Une valeur est requise'],
+             ['//input[@name="code_saisi"]', 'Une valeur est requise'],
+             ] as $case) {
+      list($path, $message) = $case;
+      $this->assertElementError($path, $message);
+    }
+  }
+
+
+  /** @test */
+  public function withBadMailShouldDisplayInvalidMail() {
+    $this->postDatas(['user_mail' => 'bad mail address']);
+    $this->assertElementError('//input[@name="user_mail"]', 'n\'est pas un email valide');
+  }
+
+
+  /** @test */
+  public function withBadCaptchaShouldDisplayInvalidCaptchaHaveEmptyValue() {
+    $this->postDatas(['code_saisi' => 'bad captcha code']);
+    $this->assertElementError('//input[@name="code_saisi"][@value=""]',
+                              'Le code anti-spam est incorrect');
+  }
+}
+
+
+
+
+class RechercheControllerReservationWithMailFormValidPostTest
+  extends RechercheControllerReservationWithMailFormPostTestCase{
+
+  protected $_mails;
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_AdminVar::set('TEXTE_MAIL_RESA', 'Thanks for your hold');
+
+    $this->postDatas(['user_name' => 'Testing user',
+                      'demande' => 'testing demande',
+                      'user_mail' => 'test@server.com',
+                      'code_saisi' => 'e2df0']);
+
+    $this->_mails = $this->_mail_transport->getSentMails();
+  }
+
+
+  protected function assertMailBodyContains($needle, $mail) {
+    $this->assertContains($needle, $mail->getBodyText(true));
+  }
+
+
+  /** @test */
+  public function validFormShouldSendTwoMails() {
+    $this->assertCount(2, $this->_mails);
+  }
+
+
+  /** @test */
+  public function mailsShouldHaveHoldQuerySubject() {
+    foreach($this->_mails as $mail)
+      $this->assertEquals('Demande de réservation de document', $mail->getSubject());
+  }
+
+
+  /** @test */
+  public function firstMailRecipientShouldBeLibrary() {
+    $this->assertContains('my@library.com', $this->_mails[0]->getRecipients());
+  }
+
+
+  /** @test */
+  public function firstMailFromShouldBeUser() {
+    $this->assertEquals('test@server.com', $this->_mails[0]->getFrom());
+  }
+
+
+  /** @test */
+  public function firstMailBodyShouldContainsUserName() {
+    $this->assertMailBodyContains('Testing user', $this->_mails[0]);
+  }
+
+
+  /** @test */
+  public function firstMailBodyShouldContainsRecordInfo() {
+    foreach(['Test record', 'P TRA', 'Test author', 'Test editor'] as $info)
+      $this->assertMailBodyContains($info, $this->_mails[0]);
+  }
+
+
+  /** @test */
+  public function firstMailBodyShouldUserMessage() {
+    $this->assertMailBodyContains('testing demande', $this->_mails[0]);
+  }
+
+
+  /** @test */
+  public function secondMailRecipientShouldBeUser() {
+    $this->assertContains('test@server.com', $this->_mails[1]->getRecipients());
+  }
+
+
+  /** @test */
+  public function secondMailFromShouldBeNobody() {
+    $this->assertEquals('nobody@noreply.fr', $this->_mails[1]->getFrom());
+  }
+
+
+  /** @test */
+  public function secondMailBodyShouldContainsTexteMailResa() {
+    $this->assertMailBodyContains('Thanks for your hold', $this->_mails[1]);
+  }
+
+
+  /** @test */
+  public function shouldRedirectToRecordView() {
+    $this->assertRedirectTo('/opac/recherche/viewnotice/id/550171?type_doc=1');
+  }
+}
\ No newline at end of file