diff --git a/FEATURES/68187 b/FEATURES/68187
new file mode 100644
index 0000000000000000000000000000000000000000..df9df03c4c44760f1c47907ac49e5bc84e794bea
--- /dev/null
+++ b/FEATURES/68187
@@ -0,0 +1,10 @@
+        '68187' =>
+            ['Label' => $this->_('Prêt Numérique en Bibliothqe avec diliCOM'),
+             'Desc' => $this->_('Prêt Numérique en Bibliothèque (PNB) est un dispositif interprofessionnel d\'accès à la lecture numérique en bibliothèques publiques.'),
+             'Image' => 'https://pnb-dilicom.centprod.com/documentation/lib/tpl/dilicom/images/Image_header_wiki_PNB.jpg',
+             'Video' => '',
+             'Category' => $this->_('Ressources numériques'),
+             'Right' => function($feature_description, $user) {return true;},
+             'Wiki' => 'http://wiki.bokeh-library-portal.org/index.php/Configuration_du_PNB_Dilicom',
+             'Test' => '',
+             'Date' => '2018-09-04'],
\ No newline at end of file
diff --git a/VERSIONS_WIP/68187 b/VERSIONS_WIP/68187
new file mode 100644
index 0000000000000000000000000000000000000000..574adf806d3c239e27ec3b9e9e365558a0355b78
--- /dev/null
+++ b/VERSIONS_WIP/68187
@@ -0,0 +1,2 @@
+ - ticket #68187 : Ressources numériques : modification du connecteur PNB Dilicom pour être compatible avec la version 3 du prestataire.
+ 
\ No newline at end of file
diff --git a/application/modules/opac/controllers/AbonneController.php b/application/modules/opac/controllers/AbonneController.php
index 915d58aa7d35e60c899706dd88a8f39028d23ea3..cfb930f01864d6b322febc2476c69a2bdf04b0af 100644
--- a/application/modules/opac/controllers/AbonneController.php
+++ b/application/modules/opac/controllers/AbonneController.php
@@ -1011,7 +1011,6 @@ class AbonneController extends ZendAfi_Controller_Action {
     $this->_helper->viewRenderer->setNoRender();
 
     $datas = [];
-
     $cards = new Class_User_Cards($this->_user);
     $emprunts = $cards
       ->getLoansWithOutPNB($this->_request->getParams())
diff --git a/application/modules/opac/controllers/BibNumeriqueController.php b/application/modules/opac/controllers/BibNumeriqueController.php
index c794cb8b036ec7052e815503606579f4973eb50f..19fb8c694216b764fb5f4d35cd010c6ed4b5be6b 100644
--- a/application/modules/opac/controllers/BibNumeriqueController.php
+++ b/application/modules/opac/controllers/BibNumeriqueController.php
@@ -309,7 +309,7 @@ class BibNumeriqueController extends ZendAfi_Controller_Action {
                                                                                $client_ip,
                                                                                $this->_user);
 
-    return $this->_withContentDo(json_decode($response),
+    return $this->_withContentDo($response,
                                  function($url)
                                  {
                                    $this->view->open_url = $url;
@@ -336,8 +336,7 @@ class BibNumeriqueController extends ZendAfi_Controller_Action {
     if($this->_userShouldBeRedirect())
       return;
 
-    $content = (new Class_WebService_BibNumerique_Dilicom_Hub())->loanBook(Class_Album::find($this->_getParam('id')));
-
+    $content = (new Class_WebService_BibNumerique_Dilicom_Hub())->loanBook(Class_Album::find($this->_getParam('id')), $this->_user);
 
     return $this->_withContentDo($content,
                                  function($url)
@@ -368,7 +367,7 @@ class BibNumeriqueController extends ZendAfi_Controller_Action {
     $response = (new Class_WebService_BibNumerique_Dilicom_Hub())->consultBook($album,
                                                                                $client_ip,
                                                                                Class_Users::getIdentity());
-    $content = json_decode($response);
+    $content = $response;
     if ($content->returnMessage) {
       $this->_helper->notify(implode(',', $content->returnMessage));
       return $this->_redirectToNotice($album->getNoticeId());
@@ -390,13 +389,14 @@ class BibNumeriqueController extends ZendAfi_Controller_Action {
   public function loanBookAction() {
     if ($this->_redirectToLogin())
       return ;
+
     if (!$this->_user->hasRightAccessDilicom())
       return;
 
     if(!$album = Class_Album::find($this->_getParam('id')))
       return $this->_redirectToReferer();
 
-    $content = (new Class_WebService_BibNumerique_Dilicom_Hub())->loanBook($album);
+    $content = (new Class_WebService_BibNumerique_Dilicom_Hub())->loanBook($album, $this->_user);
 
     if ($content->returnMessage) {
       $this->_helper->notify(implode(',', $content->returnMessage));
diff --git a/application/modules/telephone/views/scripts/recherche/ressourcesnumeriques.phtml b/application/modules/telephone/views/scripts/recherche/ressourcesnumeriques.phtml
index 39359e51cc07e3e597ce1e979a4a91d1e783ef84..7382cbf2d5f0b60be3aa107b67f9eb76f7a7d052 100644
--- a/application/modules/telephone/views/scripts/recherche/ressourcesnumeriques.phtml
+++ b/application/modules/telephone/views/scripts/recherche/ressourcesnumeriques.phtml
@@ -3,4 +3,3 @@ echo $this->toolbar($this->_("Livre numérisé"),
                     ['action' => 'simple']);
 echo $this->tag('h1', $this->notice->getTitrePrincipal());
 echo $this->renderAlbum($this->notice->getAlbum());
-?>
diff --git a/library/Class/Album/UsageConstraint.php b/library/Class/Album/UsageConstraint.php
index a9cb91d4cbd2a7f4ed09032311b3383dce66bd35..631d8eafea16df521e89c0806e33d732ba2dd310 100644
--- a/library/Class/Album/UsageConstraint.php
+++ b/library/Class/Album/UsageConstraint.php
@@ -33,6 +33,7 @@ class Class_Album_UsageConstraint extends Storm_Model_Abstract {
     ORDER_DATE = 'order_date',
     END_DATE = 'end_date';
 
+
   protected
     $_table_name = 'album_usage_constraints',
     $_belongs_to = ['item' => ['model' => 'Class_Album_Item']],
@@ -40,6 +41,7 @@ class Class_Album_UsageConstraint extends Storm_Model_Abstract {
                                   'usage_type' => null],
     $_datas;
 
+
   public function beforeSave() {
     $this->setSerializedDatas($this->getDatas()->serialized());
   }
@@ -146,7 +148,7 @@ class Class_Album_UsageConstraint extends Storm_Model_Abstract {
   public function numberOfSimultaneousLoansRemaning() {
     return $this->findItemDoIfNone(
                                    function($item) {
-                                     return $this->getLoanAllowedNumberOfUsers() - $item->getLoanCount();
+                                     return $this->getLoanAllowedNumberOfUsers() - abs($item->getLoanCount());
                                    },
                                    false );
   }
diff --git a/library/Class/Album/UsageConstraints.php b/library/Class/Album/UsageConstraints.php
index 2f173c3527ed09f66056ff54fac83ea928af69f2..6c4197b7afe736ed2d1ba6fdc5180d2c4fe3206e 100644
--- a/library/Class/Album/UsageConstraints.php
+++ b/library/Class/Album/UsageConstraints.php
@@ -162,5 +162,4 @@ class Class_Album_UsageConstraints extends Storm_Model_Collection_Abstract {
   public function isAvailable() {
     return $this->getAvailabilityRemainingDaysBeforeEndDate() >= $this->getLoanDuration();
   }
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/Class/Loan/Pnb.php b/library/Class/Loan/Pnb.php
index ceb8f5a60df45d1a88c6e7282d1b75a1328219da..a62cc93ca09400044ebe00f2d7cb97b895ccea75 100644
--- a/library/Class/Loan/Pnb.php
+++ b/library/Class/Loan/Pnb.php
@@ -75,6 +75,14 @@ class Class_Loan_PnbLoader extends Storm_Model_Loader {
   }
 
 
+  public function findFirstReturningLoanForItem($item) {
+    return Class_Loan_Pnb::findFirstBy(['order_line_id' => $item->getOrderLineId(),
+                                        'order' => 'expected_return_date',
+                                        'where' => sprintf('expected_return_date > "%s"', $this->_getDate())]);
+  }
+
+
+
   protected function _applyOngoingAndDo($params, $closure) {
     $prefix = isset($params['where']) ? '(' . $params['where'] . ') and ' : '';
     $params['where'] = $prefix . 'expected_return_date > "' . $this->_getDate() . '"';
@@ -96,7 +104,8 @@ class Class_Loan_Pnb extends Storm_Model_Abstract {
   protected
     $_table_name = 'loan_pnb',
     $_loader_class = 'Class_Loan_PnbLoader',
-    $_belongs_to = ['user' => ['model' => 'Class_Users']];
+    $_belongs_to = ['user' => ['model' => 'Class_Users']],
+    $_default_attribute_values = ['expected_return_date' => ''];
 
 
   public function getAlbum() {
diff --git a/library/Class/Testing/PhpCommand.php b/library/Class/Testing/PhpCommand.php
index 8c974a67afca16368b6472bb17c826d6d1b140a0..4b44404aae345182b39662a326ecab24d0b3ada2 100644
--- a/library/Class/Testing/PhpCommand.php
+++ b/library/Class/Testing/PhpCommand.php
@@ -22,7 +22,9 @@
 class Class_Testing_PhpCommand extends Class_Testing_FileSystem {
   public function __construct() {
     $this->_known_functions = array_merge($this->_known_functions,
-                                          ['extension_loaded',
+                                          ['rand',
+                                           'hash',
+                                           'extension_loaded',
                                            'libxml_use_internal_errors',
                                            'libxml_get_errors']);
   }
diff --git a/library/Class/WebService/BibNumerique/Dilicom/Hub.php b/library/Class/WebService/BibNumerique/Dilicom/Hub.php
index e7616f5a0ea510b86bc90c6cca94394fd46d8a5b..622ca35eb69941e22d934611625d0975dc878a0b 100644
--- a/library/Class/WebService/BibNumerique/Dilicom/Hub.php
+++ b/library/Class/WebService/BibNumerique/Dilicom/Hub.php
@@ -21,7 +21,7 @@
 
 
 class Class_WebService_BibNumerique_Dilicom_Hub extends Class_WebService_Abstract {
-  use Trait_Translator;
+  use Trait_Translator, Trait_StaticPhpCommand;
 
   const STATUS_OK = 'OK';
 
@@ -29,28 +29,23 @@ class Class_WebService_BibNumerique_Dilicom_Hub extends Class_WebService_Abstrac
 
 
   public function getLoanStatus($item) {
-    return json_decode($this->dilicomCall('getLoanStatus',
-                                          ['orderLineId[0]' => $item->getOrderLineId()]));
-  }
-
-
-  public function updateItemStatus($item) {
-    $content = $this->getLoanStatus($item);
-
-    if (isset($content->loanResponseLine[0])) {
-      $simultaneous_users_remaining = $content->loanResponseLine[0]->nus1;
+    if (!$response = $this->_dilicomCall('getLoanStatus',
+                                         ['orderLineId[0]' => (string) $item->getOrderLineId(),
+                                          'returnEndedLoan' => false]))
+      return $this->_error($this->_('Mise à jour des informations du prêt "%s" impossible. Le service "getLoanStatus" n\'a rien renvoyé.',
+                                    $item->getOrderLineId()));
 
-      $item->setLoanCount($item->getUsageConstraints()->getLoanMaxNumberOfUsers() - $simultaneous_users_remaining);
-      $item->setQuantity($item->getUsageConstraints()->getLoanQuantity() - $content->loanResponseLine[0]->nta);
-      $item->save();
-    }
+    if (!$decoded_response = json_decode($response))
+      return $this->_error($this->_('Mise à jour des informations du prêt "%s" impossible. Le service "getLoanStatus" n\'a pas renvoyé un document valide.',
+                                    $item->getOrderLineId()));
 
-    return $content;
+    return $decoded_response;
   }
 
 
   public function updateStatus($album) {
-    return array_map([$this, 'updateItemStatus'], $album->getItems());
+    array_map([$this, '_updateItemStatus'], $album->getItems());
+    return $this;
   }
 
 
@@ -59,7 +54,10 @@ class Class_WebService_BibNumerique_Dilicom_Hub extends Class_WebService_Abstrac
     if (!$loans)
       return 0;
 
-    $ids = array_map(function($loan) {return $loan->getId(); },
+    $ids = array_map(function($loan)
+                     {
+                       return $loan->getId();
+                     },
                      $loans);
 
     /** in order to lower http requests on Dilicom Web services */
@@ -77,10 +75,128 @@ class Class_WebService_BibNumerique_Dilicom_Hub extends Class_WebService_Abstrac
   }
 
 
+  public function loanBook($album, $user) {
+    $currently_loaned_by_user = $this->_isAlbumCurrentlyLoanedByUser($album, $user);
+    if (!$currently_loaned_by_user->returnMessage)
+      return $currently_loaned_by_user;
+
+    $loanable_by_user_response = $this->_isAlbumLoanableByUser($album, $user);
+    if ($loanable_by_user_response->returnMessage)
+      return $loanable_by_user_response;
+
+    $album_loanable = $this->_isAlbumLoanable($album);
+    if ($album_loanable->returnMessage)
+      return $album_loanable;
+
+    if (!$item = $this->_getFirstLoanableItemOrLast($album))
+      return $this->_error($this->_('La consultation du document est impossible.'));
+
+    $loan = Class_Loan_Pnb::newInstance(['subscriber_id' => $user->getIdabon(),
+                                         'user_id' => $user->getId(),
+                                         'loan_date' => $this->_startDate(),
+                                         'expected_return_date' => $this->_endLoanDate($item),
+                                         'record_origin_id' => $album->getIdOrigine(),
+                                         'order_line_id' => (string) $item->getOrderLineId()]);
+    $loan->save();
+
+    $response = $this->_dilicomCall('loanBook',
+                                    ['glnLoaner' => $user->getBibGLN(),
+                                     'UserInfo.year' => $this->getPhpCommand()->rand(1900, 2000),
+                                     'UserInfo.gender' => (1 == ($value = $this->getPhpCommand()->rand(1, 2))) ? 'F': 'H',
+                                     'DRMinfo.readerPass' => substr(base64_encode($this->getPhpCommand()->hash('sha256', '0000')), 0, 63) ,
+                                     'DRMinfo.readerHint' => $this->_('Votre login est "drm" et votre mot de passe est "0000"'),
+                                     'DRMinfo.readerId' => 'drm',
+                                     'orderLineId' => (string) $item->getOrderLineId(),
+                                     'loanId' =>  $loan->getId(),
+                                     'ean13' => $album->getISBN(),
+                                     'accessMedium' => 'DOWNLOAD',
+                                     'localization' => 'EX_SITU',
+                                     'loanEndDate' => $this->_endLoanDate($item)
+                                    ]);
+
+    if (!$response) {
+      $loan->delete();
+      return $this->_error($this->_('Emprunt impossible. Le service "loanBook" n\'a rien renvoyé.'));
+    }
+
+    if (!$content = json_decode($response)) {
+      $loan->delete();
+      return $this->_error($this->_('Emprunt impossible. Le service "loanBook" n\'a pas renvoyé un document valide.'));
+    }
+
+    if (isset($content->returnMessage) && ($error = $content->returnMessage)) {
+      $loan->delete();
+      return $this->_error($this->_('Emprunt impossible. Le service "loanBook" a renvoyé une erreur : "%s"', implode(',', $error)));
+    }
+
+    if (isset($content->link) && ($link = $content->link) && isset($link->url) && ($url = $link->url))
+    $loan->setLoanLink($url)->save();
+    $this->updateStatus($album);
+    return $content;
+  }
+
+
+  public function consultBook($album, $ip_address, $user) {
+    if (!$item = $this->_getFirstLoanableItemOrLast($album))
+      return $this->_error($this->_('La consultation du document est impossible.'));
+
+    $response = $this->_dilicomCall('consultBook',
+                                    ['glnLoaner' => $user->getBibGLN(),
+                                     'UserInfo.year' => $this->getPhpCommand()->rand(1900, 2000),
+                                     'UserInfo.gender' => (1 == ($value = $this->getPhpCommand()->rand(1, 2))) ? 'F': 'H',
+                                     'orderLineId' => (string) $item->getOrderLineId(),
+                                     'loanId' =>  implode('',
+                                                          [base_convert($this->getTimeSource()->time(), 10, 36),
+                                                           Class_Users::currentUserId(),
+                                                           $album->getId()]),
+                                     'ean13' => $album->getISBN(),
+                                     'accessMedium' => 'STREAMING',
+                                     'localization' => 'IN_SITU',
+                                     'consultEndDate' => $this->_endConsultDate(),
+                                     'ipAddress' => $ip_address,
+                                     'returnMessage' => $this->_('La requête "consultBook" n\'a pas été traitée correctement.'),
+                                    ]);
+
+    if (!$response)
+      return $this->_error($this->_('Consultation impossible. Le service "consultBook" n\'a rien renvoyé.'));
+
+    if (!$content = json_decode($response))
+      return $this->_error($this->_('Consultation impossible. Le service "consultBook" n\'a pas renvoyé un document valide.'));
+
+    if ($error = $content->returnMessage)
+      return $this->_error($this->_('Consultation impossible. Le service "consultBook" a renvoyé une erreur : "%s"', implode(',', $error)));
+
+    return $content;
+  }
+
+
+  public function declareIp($ips = []) {
+    return $this->_dilicomCall('declareIp', $this->_buildIps($ips));
+  }
+
+
+  public function isAlbumLoanableBy($album, $user) {
+    $loanable = $this->_isAlbumLoanable($album);
+    if ($error = $loanable->returnMessage)
+      return $loanable;
+
+    $currently_loaned_by_user = $this->_isAlbumCurrentlyLoanedByUser($album, $user);
+    if (!$currently_loaned_by_user->returnMessage)
+      return $currently_loaned_by_user;
+
+    return $this->_isAlbumLoanableByUser($album, $user);
+  }
+
+
   /** @return int number of updated loans */
   protected function _updateLoansPageReturnDate($page) {
-    if ((!$response = $this->_getEndedLoans($page))
-        || !$this->_isResponseOk($response))
+    if (!$response = $this->_getEndedLoans($page))
+      return 0;
+
+    if (!$this->_isResponseOk($response))
+      return 0;
+
+    if (!isset($response->loanEndedResponseLine))
       return 0;
 
     $mapping = [];
@@ -116,176 +232,183 @@ class Class_WebService_BibNumerique_Dilicom_Hub extends Class_WebService_Abstrac
       $i++;
     }
 
-    return json_decode($this->dilicomCall('getEndedLoans', $params));
+    return json_decode($this->_dilicomCall('getEndedLoans', $params));
   }
 
 
-  public function loanBook($album) {
-    $user = Class_Users::getIdentity();
-    $this->_now = $this->getCurrentTime();
+  protected function _getFirstLoanableItemOrLast($album) {
+    $items = $album->getItems();
+    foreach($items as $item) {
+      if ($item->isLoanable())
+        return $item;
+    }
+    return end($items);
+  }
 
-    $loan = $user->getPNBLoans()
-                 ->detect(
-                          function($item) use($album) {
-                            return $item->getRecordOriginId() == $album->getIdOrigine();
-                          });
 
-    if ($loan
-        && (strtotime($loan->getExpectedReturnDate()) > $this->_now))
-      return (object) ['link' => (object) ['url' => $loan->getLoanLink()],
-                       'returnMessage' => []];
+  protected function _endLoanDate($item) {
+    return $this->_iso8601($this->_getCalledTime() +
+                           $item->getUsageConstraints()->getLoanDuration() * 3600 * 24);
+  }
 
-    $this->updateStatus($album);
 
-    $item = $this->getFirstLoanableItemOrLast($album);
+  protected function _endConsultDate() {
+    return $this->_iso8601($this->_getCalledTime() + 3600);
+  }
 
-    if (!$item->isAvailable())
-      return (object) ['returnMessage' => [$this->_('Emprunt impossible. La ressource n\'est plus disponible.')]];
 
-    if (!$item->getUsageConstraints()->hasSimultaneousLoanRemaining())
-      return (object) ['returnMessage' => [$this->_('Emprunt impossible. Le nombre d\'emprunts simultanés est atteint.')]];
+  protected function _startDate() {
+    return $this->_iso8601($this->_getCalledTime());
+  }
 
-    if (!$item->getUsageConstraints()->hasAvailableQuantity())
-      return (object) ['returnMessage' => [$this->_('Emprunt impossible. Le nombre d\'emprunts disponible est épuisé.')]];
 
-    if (!$item->getUsageConstraints()->isAValidOffer())
-      return (object) ['returnMessage' => [$this->_('Emprunt impossible. L\'emprunt du document n\'est plus disponible.')]];
+  protected function _iso8601($timestamp) {
+    return date(DATE_ISO8601, $timestamp);
+  }
 
-    $quota = (int)Class_AdminVar::getValueOrDefault('DILICOM_PNB_MAX_LOAN_PER_USER');
-    if ($user->getPNBLoans()->count() >= $quota)
-      return (object) ['returnMessage' => [$this->_('Emprunt impossible. Vous avez atteint votre quota de %s emprunts.', $quota)]];
 
+  protected function _dilicomCall($service, $params) {
+    $response = $this->httpGet($this->_buildUrl($service, $params),
+                               ['auth' => ['user' => Class_AdminVar::get('DILICOM_PNB_GLN_COLLECTIVITE'),
+                                           'password' => Class_AdminVar::get('DILICOM_PNB_PWD_COLLECTIVITE')]]);
 
-    $loan = Class_Loan_Pnb::newInstance(['subscriber_id' => $user->getIdabon(),
-                                         'user_id' => $user->getId(),
-                                         'loan_date' => $this->startDate(),
-                                         'expected_return_date' => $this->endLoanDate($item),
-                                         'record_origin_id' => $album->getIdOrigine(),
-                                         'order_line_id' => $item->getOrderLineId()]);
-    $loan->save();
+    $this->_log();
+    return $response;
+  }
 
-    $response = $this->dilicomCall('loanBook',
-                                   ['orderLineId' => $item->getOrderLineId(),
-                                    'accessMedium' => 'DOWNLOAD',
-                                    'glnColl' => Class_AdminVar::get('DILICOM_PNB_GLN_COLLECTIVITE'),
-                                    'loanerColl' => $user->getBibGLN(),
-                                    'localization' => 'EX_SITU',
-                                    'loanEndDate' => urlencode($this->endLoanDate($item)),
-                                    'ean13' => $album->getISBN(),
-                                    'loanId' =>  $loan->getId(),
-                                   ]);
 
-    $content = json_decode($response);
+  protected function _log() {
+    if (($ig = Zend_Controller_Front::getInstance()
+         ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
+        && $ig->isEnabled())
+      $ig->log();
+  }
+
 
-    if ($content->returnMessage) {
-      $loan->delete();
-      return $content;
-    }
+  protected function _buildUrl($service, $params) {
+    $params = array_merge([
+                           'login' => Class_AdminVar::get('DILICOM_PNB_GLN_COLLECTIVITE'),
+                           'password' => Class_AdminVar::get('DILICOM_PNB_PWD_COLLECTIVITE'),
+                           'glnContractor' => Class_AdminVar::get('DILICOM_PNB_GLN_CONTRACTOR')],
+                          $params);
 
-    $loan->setLoanLink($content->link->url)->save();
-    $this->updateStatus($album);
-    return $content;
+    return $this->_getDilicomUrl() . $service . '?' . http_build_query($params);
   }
 
 
-  protected function getFirstLoanableItemOrLast($album) {
-    $items = $album->getItems();
-    foreach($items as $item) {
-      if ($item->isLoanable())
-        return $item;
-    }
-    return end($items);
+  protected function _getDilicomUrl() {
+    return Class_AdminVar::get('DILICOM_PNB_SERVER_URL') . '/v3/pnb-numerique/json/';
   }
 
 
-  public function consultBook($album, $ip_address, $user) {
-    $this->_now = $this->getCurrentTime();
-    $item = $this->getFirstLoanableItemOrLast($album);
-    return $this->dilicomCall('consultBook',
-                              ['orderLineId' => $item->getOrderLineId(),
-                               'accessMedium' => 'STREAMING',
-                               'localization' => 'IN_SITU',
-                               'consultEndDate' => urlencode($this->endConsultDate()),
-                               'ean13' => $album->getISBN(),
-                               'ipAddress' => $ip_address,
-                               'glnColl' => Class_AdminVar::get('DILICOM_PNB_GLN_COLLECTIVITE'),
-                               'loanerColl' => $user->getBibGLN(),
-                               'loanId' =>  implode('',
-                                                    [base_convert($this->getTimeSource()->time(), 10, 36),
-                                                     Class_Users::currentUserId(),
-                                                     $album->getId()])
-                              ]);
+  protected function _buildIps($ips) {
+    $ips_param = [];
+    for($i = 0; $i < count($ips); $i++) {
+      $ips_param['ips[' . $i . ']'] = $ips[$i];
+    }
+    return $ips_param;
   }
 
 
-  public function declareIp($ips = []) {
-    return $this->dilicomCall('declareIp', $this->buildIps($ips));
-  }
+  protected function _isAlbumLoanable($album) {
+    $this->updateStatus($album);
 
+    if (!$item = $this->_getFirstLoanableItemOrLast($album))
+      return $this->_error($this->_('Album invalide'));
 
-  public function endLoanDate($item) {
-    return $this->iso8601($this->_now +
-                          $item->getUsageConstraints()->getLoanDuration() * 3600 * 24);
-  }
+    if (!$item->isAvailable())
+      return $this->_error($this->_('Emprunt impossible. La ressource n\'est plus disponible.'));
 
+    if (!$item->getUsageConstraints()->hasSimultaneousLoanRemaining())
+      return $this->_notAvailableError($item);
 
-  public function endConsultDate() {
-    return $this->iso8601($this->_now + 3600);
-  }
+    if (!$item->getUsageConstraints()->hasAvailableQuantity())
+      return $this->_error($this->_('Emprunt impossible. Le nombre d\'emprunts disponible est épuisé.'));
 
+    if (!$item->getUsageConstraints()->isAValidOffer())
+      return $this->_error($this->_('Emprunt impossible. L\'emprunt du document n\'est plus disponible.'));
 
-  public function startDate() {
-    return $this->iso8601($this->_now);
+    return $this->_success('');
   }
 
 
-  protected function iso8601($timestamp) {
-    return date(DATE_ISO8601, $timestamp);
-  }
+  protected function _notAvailableError($item) {
+    $message = $this->_('Emprunt impossible. Le nombre d\'emprunts simultanés pour ce document est atteint.');
 
+    if (!$loan = Class_Loan_Pnb::findFirstReturningLoanForItem($item))
+      return $this->_error($message);
 
-  protected function dilicomCall($service, $params) {
-    $response = $this->httpGet($this->buildUrl($service, $params),
-                               ['auth' => ['user' =>Class_AdminVar::get('DILICOM_PNB_GLN_COLLECTIVITE'),
-                                           'password' => Class_AdminVar::get('DILICOM_PNB_PWD_COLLECTIVITE')]]);
+    $message .= ' ' . $this->_('Le prochain emprunt sera possible le %s',
+                               strftime($this->_('%A %d %B à %Hh%M'),
+                                        strtotime($loan->getExpectedReturnDate())));
 
-    $this->_log();
-    return $response;
+    return $this->_error($message);
   }
 
 
-  protected function _log() {
-    if (($ig = Zend_Controller_Front::getInstance()
-         ->getPlugin('ZendAfi_Controller_Plugin_InspectorGadget'))
-        && $ig->isEnabled())
-      $ig->log();
+  protected function _isAlbumCurrentlyLoanedByUser($album, $user) {
+    if (!$user)
+      return $this->_error($this->_('L\'emprunt est impossible car vous n\'êtes pas connecté.'));
+
+    if (!$item = $this->_getFirstLoanableItemOrLast($album))
+      return $this->_error($this->_('Album invalide'));
+
+    $loan = $user->getPNBLoans()
+                 ->detect(
+                          function($item) use($album) {
+                            return $item->getRecordOriginId() == $album->getIdOrigine();
+                          });
+    if (!$loan)
+      return $this->_error($this->_('Pas d\'emprunt en cours pour cet album.'));
+
+    return strtotime($loan->getExpectedReturnDate()) > $this->_getCalledTime()
+      ? $this->_success($loan->getLoanLink())
+      : $this->_error($this->_('Pas d\'emprunt en cours pour cet album.'));
   }
 
 
-  protected function buildUrl($service, $params) {
-    $params = array_merge(['glnContractor' => Class_AdminVar::get('DILICOM_PNB_GLN_CONTRACTOR')],
-                          $params);
+  protected function _isAlbumLoanableByUser($album, $user) {
+    if (!$user)
+      return $this->_error($this->_('L\'emprunt est impossible car vous n\'êtes pas connecté.'));
 
-    $parts = [];
-    foreach($params as $key => $value) {
-      $parts[] = $key . '=' . $value;
-    }
+    $quota = (int) Class_AdminVar::getValueOrDefault('DILICOM_PNB_MAX_LOAN_PER_USER');
 
-    return $this->getDilicomUrl() . $service . '?' . implode('&', $parts);
+    return $user->getPNBLoans()->count() >= $quota
+      ? $this->_error($this->_('Emprunt impossible. Vous avez atteint votre quota de %s emprunts.', $quota))
+      : $this->_success('');
   }
 
 
-  protected function getDilicomUrl() {
-    return Class_AdminVar::get('DILICOM_PNB_SERVER_URL') . '/v2/pnb-numerique/json/';
+  protected function _success($url) {
+    return (object) ['link' => (object) ['url' => $url],
+                     'returnMessage' => []];
   }
 
 
-  protected function buildIps($ips) {
-    $ips_param = [];
-    for($i = 0; $i < count($ips); $i++) {
-      $ips_param['ips[' . $i . ']'] = $ips[$i];
+  protected function _error($message) {
+    return (object) ['returnMessage' => [$message]];
+  }
+
+
+  protected function _updateItemStatus($item) {
+    $content = $this->getLoanStatus($item);
+
+    if (isset($content->loanResponseLine[0])) {
+      $simultaneous_users_remaining = $content->loanResponseLine[0]->nus1;
+
+      $item->setLoanCount($item->getUsageConstraints()->getLoanMaxNumberOfUsers() - $simultaneous_users_remaining);
+      $item->setQuantity($item->getUsageConstraints()->getLoanQuantity() - $content->loanResponseLine[0]->nta);
+      $item->save();
     }
-    return $ips_param;
+
+    return $this;
+  }
+
+
+  protected function _getCalledTime() {
+    if (!$this->_now)
+      $this->_now = $this->getCurrentTime();
+
+    return $this->_now;
   }
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/Class/WebService/BibNumerique/Dilicom/PNBOffersFile.php b/library/Class/WebService/BibNumerique/Dilicom/PNBOffersFile.php
index 483e011560719c33c3956dc958080cc0dace4f51..59452e8ac0792d7e2ca4e09f42be45b3143a8da5 100644
--- a/library/Class/WebService/BibNumerique/Dilicom/PNBOffersFile.php
+++ b/library/Class/WebService/BibNumerique/Dilicom/PNBOffersFile.php
@@ -96,6 +96,4 @@ class Class_WebService_BibNumerique_Dilicom_PNBOffersFile {
                         ->getAvailabilityConstraint()
                         ->setDuration($data);
   }
-}
-
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/Class/WebService/SIGB/Opsys.php b/library/Class/WebService/SIGB/Opsys.php
index 351b8866d809dfea43b7ea937e8cc874dd50fa9f..72986275efb60d0cdcf4e684aa52538d6b063441 100644
--- a/library/Class/WebService/SIGB/Opsys.php
+++ b/library/Class/WebService/SIGB/Opsys.php
@@ -96,8 +96,12 @@
 
 
 class Class_WebService_SIGB_Opsys {
-  protected static $service_options;
-  protected static $service;
+
+  protected static
+    $service_options,
+    $service;
+
+  protected $_service_factory;
 
 
   public static function reset() {
@@ -146,22 +150,34 @@ class Class_WebService_SIGB_Opsys {
       self::$service_options = array();
   }
 
+
   public static function getServiceOptions(){
     self::createServiceOptions();
     return self::$service_options;
   }
 
-  public function createService($url_aloes, $with_catalog_web = true, $is_reserver_retrait_bib_abonne = false){
-    return $this->newOpsysServiceFactory()->createOpsysService($url_aloes,
-                                                               $with_catalog_web,
-                                                               $is_reserver_retrait_bib_abonne,
-                                                               self::getServiceOptions());
+
+  public function createService($url_aloes,
+                                $with_catalog_web = true,
+                                $is_reserver_retrait_bib_abonne = false) {
+    return $this->newOpsysServiceFactory()
+                ->createOpsysService($url_aloes,
+                                     $with_catalog_web,
+                                     $is_reserver_retrait_bib_abonne,
+                                     self::getServiceOptions());
   }
 
-  public function newOpsysServiceFactory(){
-    return new Class_WebService_SIGB_Opsys_ServiceFactory();
+
+  public function newOpsysServiceFactory() {
+    if (!$this->_service_factory)
+      $this->_service_factory = new Class_WebService_SIGB_Opsys_ServiceFactory();
+
+    return $this->_service_factory;
   }
-}
 
 
-?>
+  public function setServiceFactory($instance) {
+    $this->_service_factory = $instance;
+    return $this;
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/RenderAlbum.php b/library/ZendAfi/View/Helper/RenderAlbum.php
index c96b359367d067e9da06f99eb0e61b41b7ad3c94..47e2cf37a35ec172f838ae681e9e54a63ddbfc99 100644
--- a/library/ZendAfi/View/Helper/RenderAlbum.php
+++ b/library/ZendAfi/View/Helper/RenderAlbum.php
@@ -38,6 +38,4 @@ class ZendAfi_View_Helper_RenderAlbum extends ZendAfi_View_Helper_BaseHelper {
   public function renderAlbumHelper($album) {
     return $album->renderOn($this->view);
   }
-}
-
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/TagDilicomWidget.php b/library/ZendAfi/View/Helper/TagDilicomWidget.php
index 19983f644300af699e669c844f6709d078b20c90..738c93af015bb5c2c37b23f6dfc2641be49755c7 100644
--- a/library/ZendAfi/View/Helper/TagDilicomWidget.php
+++ b/library/ZendAfi/View/Helper/TagDilicomWidget.php
@@ -21,21 +21,23 @@
 
 
 class ZendAfi_View_Helper_TagDilicomWidget extends ZendAfi_View_Helper_BaseHelper {
+
   protected
     $_album,
     $_user;
 
+
   public function tagDilicomWidget($album) {
     $this->_album = $album;
-    return $this->_renderPNBActions() . $this->renderBookPreview($album);
+    $this->_user = Class_Users::getIdentity();
+    return $this->_renderPNBActions() . $this->_renderBookPreview($album);
   }
 
 
   protected function _renderPNBActions() {
-    $this->_user = Class_Users::getIdentity();
     if (!$this->_user || ($this->_user->hasRightAccessDilicom() &&
                           $this->_user->getBibGLN()))
-      return $this->getConsultBookAnchor() . $this->getLoanBookAnchor();
+      return $this->_getConsultBookAnchor() . $this->_getLoanBookAnchor();
 
     if (!$this->_user->hasRightAccessDilicom())
       return $this->_tag('p',
@@ -52,7 +54,7 @@ class ZendAfi_View_Helper_TagDilicomWidget extends ZendAfi_View_Helper_BaseHelpe
   }
 
 
-  public function renderBookPreview($album) {
+  protected function _renderBookPreview($album) {
     if (!$url = $this->_previewUrl())
       return '';
 
@@ -63,17 +65,17 @@ class ZendAfi_View_Helper_TagDilicomWidget extends ZendAfi_View_Helper_BaseHelpe
   }
 
 
-  public function _previewUrl() {
+  protected function _previewUrl() {
     return Class_Url::secureIfNeeded($this->_album->getExternalURI());
   }
 
 
-  protected function getConsultBookAnchor() {
-    return $this->getDilicomAnchor(['controller' => 'bib-numerique',
-                                    'action' => $this->_getConsultBookAction(),
-                                    'id' => $this->_album->getId()],
-                                   $this->_('Consulter le livre en ligne (depuis la médiathèque)'),
-                                   ['data-popup' => 'true']);
+  protected function _getConsultBookAnchor() {
+    return $this->_anchor(['controller' => 'bib-numerique',
+                           'action' => $this->_getConsultBookAction(),
+                           'id' => $this->_album->getId()],
+                          $this->_('Consulter le livre en ligne (depuis la médiathèque)'),
+                          ['data-popup' => 'true']);
   }
 
 
@@ -87,20 +89,38 @@ class ZendAfi_View_Helper_TagDilicomWidget extends ZendAfi_View_Helper_BaseHelpe
   }
 
 
-  protected function getLoanBookAnchor() {
-    return $this->getDilicomAnchor(['controller' => 'bib-numerique',
-                                    'action' => $this->_getLoanBookAction(),
-                                    'id' => $this->_album->getId()],
-                                   $this->_('Emprunter le livre au format EPUB'),
-                                   ['data-popup' => 'true']);
+  protected function _getLoanBookAnchor() {
+    $loanBookAnchor = $this->_anchor(['controller' => 'bib-numerique',
+                                      'action' => $this->_getLoanBookAction(),
+                                      'id' => $this->_album->getId()],
+                                     $this->_('Emprunter le livre au format EPUB'),
+                                     ['data-popup' => 'true']);
+
+    if (!$this->_user)
+      return $loanBookAnchor;
+
+    $hub = new Class_WebService_BibNumerique_Dilicom_Hub();
+    $loanable = $hub->isAlbumLoanableBy($this->_album, $this->_user);
+    return ($error = $loanable->returnMessage)
+      ? $this->_errorButton($error[0])
+      : $loanBookAnchor;
+  }
+
+
+  protected function _errorButton($message) {
+    return $this->_tag('div',
+                       $this->_tag('span' ,
+                                   $message,
+                                   ['class' => 'error']),
+                       ['class' => 'dilicom-action']);
   }
 
 
-  protected function getDilicomAnchor($url, $label, $attribs = []) {
+  protected function _anchor($url, $label, $attribs = []) {
     return $this->_tag('div',
                        $this->view->tagAnchor($url,
                                               $label,
                                               $attribs),
                        ['class' => 'dilicom-action']);
   }
-}
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Telephone/RenderAlbum.php b/library/ZendAfi/View/Helper/Telephone/RenderAlbum.php
index c914d248b8a08b1c873b19a38ebe98b89799c36e..c82e861e15cb0ba0617b02aefa9b467ea9661939 100644
--- a/library/ZendAfi/View/Helper/Telephone/RenderAlbum.php
+++ b/library/ZendAfi/View/Helper/Telephone/RenderAlbum.php
@@ -25,6 +25,4 @@ class ZendAfi_View_Helper_Telephone_RenderAlbum extends ZendAfi_View_Helper_Rend
       ? sprintf('<div id="resnum">%s</div>', $this->renderAlbumHelper($album))
       : $this->view->_('Aucune ressource numérique trouvée.');
   }
-}
-
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/ZendAfi/View/Helper/Telephone/TagDilicomWidget.php b/library/ZendAfi/View/Helper/Telephone/TagDilicomWidget.php
index b673e497ef2d097ca2ab635adf9211754171774f..d9db97f903038c1bebe97ac1161e55cdbe0732e9 100644
--- a/library/ZendAfi/View/Helper/Telephone/TagDilicomWidget.php
+++ b/library/ZendAfi/View/Helper/Telephone/TagDilicomWidget.php
@@ -19,7 +19,9 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
+
 class ZendAfi_View_Helper_Telephone_TagDilicomWidget extends ZendAfi_View_Helper_TagDilicomWidget {
+
   protected function _getLoanBookAction() {
     return 'loan-book';
   }
@@ -30,26 +32,23 @@ class ZendAfi_View_Helper_Telephone_TagDilicomWidget extends ZendAfi_View_Helper
   }
 
 
-  public function renderBookPreview($album) {
+  protected function _renderBookPreview($album) {
     // too much performance issues
     return '';
   }
 
 
-  protected function getConsultBookAnchor() {
+  protected function _getConsultBookAnchor() {
     return '';
   }
 
 
-  protected function getLoanBookAnchor() {
-    return $this->getDilicomAnchor(['controller' => 'bib-numerique',
-                                    'action' => $this->_getLoanBookAction(),
-                                    'id' => $this->_album->getId()],
-                                   $this->_('Emprunter le livre au format EPUB'),
-                                   ['data-role' => 'button',
-                                    'data-ajax' => 'false']);
+  protected function _getLoanBookAnchor() {
+    return $this->_anchor(['controller' => 'bib-numerique',
+                           'action' => $this->_getLoanBookAction(),
+                           'id' => $this->_album->getId()],
+                          $this->_('Emprunter le livre au format EPUB'),
+                          ['data-role' => 'button',
+                           'data-ajax' => 'false']);
   }
-
-}
-
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/public/opac/css/global.css b/public/opac/css/global.css
index 3492c2d10a60fa9a13399ebb86c5e910e0119959..516939685e4b3a505a9418d65e655e6ab5b58f4a 100644
--- a/public/opac/css/global.css
+++ b/public/opac/css/global.css
@@ -3570,4 +3570,8 @@ a[href*="bookmarked-searches/notify"] img {
 
 .tools-bar a + a {
     margin-left: 10px;
+}
+
+.dilicom-action .error {
+    cursor: not-allowed;
 }
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/AlbumControllerDilicomPNBTest.php b/tests/application/modules/admin/controllers/AlbumControllerDilicomPNBTest.php
deleted file mode 100644
index 36c302bc91e676d21f23c3cbc0d5dd1c59b75b18..0000000000000000000000000000000000000000
--- a/tests/application/modules/admin/controllers/AlbumControllerDilicomPNBTest.php
+++ /dev/null
@@ -1,467 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012, 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
- */
-
-abstract class Admin_AlbumControllerDilicomPNBTestCase extends Admin_AbstractControllerTestCase {
-  protected $_storm_default_to_volatile = true;
-
-  public function setUp() {
-    parent::setUp();
-
-    RessourcesNumeriquesFixtures::activateDilicom();
-
-    $this->fixture('Class_Album',
-                   ['id' => 23,
-                    'titre' => 'Being human being',
-                    'type_doc_id' => Class_TypeDoc::DILICOM]);
-
-    $this->fixture('Class_Album_Item',
-                   ['id' => 1,
-                    'quantity' => '10',
-                    'loan_count' => '2',
-                    'album_id' => 23,
-                    'usage_constraints' =>
-                    [
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 1,
-                                     'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 45,
-                                                                        Class_Album_UsageConstraint::QUANTITY => 30,
-                                                                        Class_Album_UsageConstraint::MAX_NB_OF_USERS => 15])]),
-
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 2,
-                                     'usage_type' => Class_Album_UsageConstraint::DEVICE_SHARE_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::QUANTITY => 6])]),
-
-
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 3,
-                                     'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 2345,
-                                                                        Class_Album_UsageConstraint::ORDER_LINE_ID => '12385',
-                                                                        Class_Album_UsageConstraint::ORDER_DATE => '2015-03-30'])])
-                    ]
-                   ]);
-
-    $this->fixture('Class_Album_Item',
-                   ['id' => 2,
-                    'quantity' => '20',
-                    'loan_count' => '5',
-                    'album_id' => 23,
-                    'usage_constraints' =>
-                    [
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 1,
-                                     'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 20,
-                                                                        Class_Album_UsageConstraint::QUANTITY => 20,
-                                                                        Class_Album_UsageConstraint::MAX_NB_OF_USERS => 15])]),
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 5,
-                                     'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 20000,
-                                                                        Class_Album_UsageConstraint::ORDER_LINE_ID => '8765',
-                                                                        Class_Album_UsageConstraint::ORDER_DATE => '2015-08-29'])])
-                    ]
-                   ]);
-
-
-    $this->fixture('Class_CodifGenre',
-                   ['id' => 23,
-                    'libelle' => 'Heavy Metal']);
-
-    $this->fixture('Class_CodifSection',
-                   ['id' => 33,
-                    'libelle' => 'Espace métal']);
-
-    $fondu = $this->fixture('Class_AlbumCategorie',
-                            ['id' => 1301,
-                             'libelle' => 'Fondu']);
-
-    $this->fixture('Class_Album',
-                   ['id' => 9999,
-                    'id_origine' => 'Dilicom-3663608260879',
-                    'titre' => 'Hell is from here to eternity',
-                    'type_doc_id' => Class_TypeDoc::DILICOM,
-                    'genre' => '23',
-                    'sections' => '33',
-                    'categorie' => $fondu])
-         ->addAuthor('Iron Maiden')
-         ->addEditor('EMI')
-         ->addCollection('Temple Of Rock')
-         ->setAnnee(1992)
-      ;
-
-    $this->fixture('Class_Album_Item',
-                   ['id' => 9,
-                    'quantity' => 990000,
-                    'loan_count' => 3,
-                    'album_id' => 9999,
-                    'usage_constraints' =>
-                    [
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 1,
-                                     'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 359,
-                                                                        Class_Album_UsageConstraint::QUANTITY => 999999,
-                                                                        Class_Album_UsageConstraint::MAX_NB_OF_USERS => 1])]),
-
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 2,
-                                     'usage_type' => Class_Album_UsageConstraint::DEVICE_SHARE_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::QUANTITY => 6])]),
-
-
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 3,
-                                     'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 999999,
-                                                                        Class_Album_UsageConstraint::ORDER_LINE_ID => '584837a045ce56ef0a072a8b',
-                                                                        Class_Album_UsageConstraint::ORDER_DATE => '2015-03-30 08:08:34'])])
-                    ]
-                   ]);
-
-    $this->fixture('Class_Loan_Pnb',
-                   ['id' => 2,
-                    'record_origin_id' => 'Dilicom-3663608260879',
-                    'subscriber_id' => '000005',
-                    'user_id' => 4077,
-                    'expected_return_date' => '2017-01-20 13:57:33',
-                    'loan_date' => '2016-12-16 13:57:33',
-                    'loan_link' => 'https://pnb-dilicom.centprod.com/v2//XXXXXXXX.do',
-                    'order_line_id' => '584837a045ce56ef0a072a8b',]);
-
-    $this->fixture('Class_Loan_Pnb',
-                   ['id' => 3,
-                    'record_origin_id' => 'Dilicom-3663608260879',
-                    'subscriber_id' => '000006',
-                    'user_id' => 4078,
-                    'expected_return_date' => '2017-12-13 13:57:33',
-                    'loan_date' => '2017-11-13 13:57:33',
-                    'loan_link' => 'https://pnb-dilicom.centprod.com/v2//XXXXXXXX.do',
-                    'order_line_id' => '584837a045ce56ef0a072a8b',]);
-
-    Class_Loan_Pnb::setTimeSource(new TimeSourceForTest('2017-11-14 11:35:05'));
-
-    $this->onLoaderOfModel('Class_Loan_Pnb')
-         ->whenCalled('countByOngoingOrderLineId')->with('584837a045ce56ef0a072a8b')
-         ->answers(1);
-  }
-
-
-  public function tearDown() {
-    RessourcesNumeriquesFixtures::deactivateDilicom();
-    Class_Loan_Pnb::setTimeSource(null);
-
-    parent::tearDown();
-  }
-}
-
-
-
-class AlbumControllerDilicomPNBImportDilicomTest extends Admin_AlbumControllerDilicomPNBTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->dispatch('/admin/album/dilicom', true);
-  }
-
-
-  /** @test */
-  public function tableSorterPagerJsShouldBeLoaded() {
-    $this->assertXPath('//script[contains(@src, "tablesorter/addons/pager/jquery.tablesorter.pager.min")]');
-  }
-
-
-  /** @test */
-  public function tableSorterPagerCSSShouldBeLoaded() {
-    $this->assertXPath('//link[contains(@href, "/public/opac/js/tablesorter/addons/pager/jquery.tablesorter.pager.css")]');
-  }
-
-
-  /** @test */
-  public function tableSorterPagerShouldBeActivated() {
-    $this->assertXPathContentContains('//script',
-                                      'tablesorterPager({container:');
-  }
-
-
-  /** @test */
-  public function beingHumanTitleShouldBeInTalbe() {
-    $this->assertXPathContentContains('//table//tr/td', 'Being human being');
-  }
-
-  /** @test */
-  public function beingHumanActionShouldLinkToEditAlbum() {
-    $this->assertXPath('//table//tr/td/a[contains(@href, "/edit_album/id/23")]', $this->_response->getBody());
-  }
-
-
-  /** @test */
-  public function nbOfLoansShouldBe30() {
-    $this->assertXPathContentContains('//table//tr/td', '10 / 30');
-  }
-
-
-  /** @test */
-  public function nbOfLiveLoansShouldBe2() {
-    $this->assertXPathContentContains('//table//tr/td', '2 / 15');
-  }
-
-
-  /** @test */
-  public function nbOfRemainingDaysInLicenseShouldBe45() {
-    $this->assertXPathContentContains('//table//tr/td', '45');
-  }
-
-
-  /** @test */
-  public function orderDateShouldContains29_08_2015() {
-    $this->assertXPathContentContains('//table//tr/td', '29/08/2015');
-  }
-
-
-  /** @test */
-  public function exportCSVButtonShouldBePresent() {
-    $this->assertXPathContentContains('//div[@class="modules"]/button[contains(@data-url, "/admin/album/dilicom-export-csv")]', 'Exporter le tableau en CSV');
-  }
-
-
-  /** @test */
-  public function exportCSVLoansButtonShouldBePresent() {
-    $this->assertXPathContentContains('//div[@class="modules"]/button[contains(@data-url, "/admin/album/dilicom-export-loans-csv")]', 'Exporter l\'historique des prêts en CSV');
-  }
-
-
-  /** @test */
-  public function nbOfEternityLoansShouldBeTwoOnInfinite() {
-    $this->assertXPathContentContains('//table//tr[3]/td[2]', '2 / ∞');
-  }
-
-
-  /** @test */
-  public function nbOfEternityLiveLoansShouldBe1OnInfinite() {
-    $this->assertXPathContentContains('//table//tr[3]/td[3]', '1 / ∞');
-  }
-
-
-  /** @test */
-  public function durationOfEternityShouldBe359() {
-    $this->assertXPathContentContains('//table//tr[3]/td[4]', '359');
-  }
-
-
-  /** @test */
-  public function licenceExpirationOfEternityShouldBeInfinite() {
-    $this->assertXPathContentContains('//table//tr[3]/td[5]', '∞');
-  }
-}
-
-
-
-class AlbumControllerDilicomPNBEditTest extends Admin_AlbumControllerDilicomPNBTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->dispatch('/admin/album/editalbum/id/23', true);
-  }
-
-
-  /** @test */
-  public function inputTitreShouldContainsBeingHumanBeing() {
-    $this->assertXPath('//input[@name="titre"][@value="Being human being"]');
-  }
-
-
-  /** @test */
-  public function fieldsetUsageShouldBeVisible() {
-    $this->assertXPathContentContains('//fieldset/legend','Utilisation');
-  }
-
-
-  /** @test */
-  public function dtInUsageShouldContainsPret() {
-    $this->assertXPathContentContains('//fieldset//dl//dt', 'Prêt');
-  }
-
-
-  /** @test */
-  public function dddldtShouldContainsDuration() {
-    $this->assertXPathContentContains('//fieldset//dl/dd/dl/dt', 'Durée (j)');
-  }
-
-
-  /** @test */
-  public function dddlddShouldContains45() {
-    $this->assertXPathContentContains('//fieldset//dl/dd/dl/dd', '45');
-  }
-
-
-  /** @test */
-  public function dddldtShouldContainsDateDeCommande() {
-    $this->assertXPathContentContains('//fieldset//dl/dd/dl/dt', 'Date de commande');
-  }
-
-
-  /** @test */
-  public function dddldtShouldContainsNumeroCommande() {
-    $this->assertXPathContentContains('//fieldset//dl/dd/dl/dt', 'Numéro de commande');
-  }
-
-
-  /** @test */
-  public function dtInUsageShouldContainsAuthorizedDevices() {
-    $this->assertXPathContentContains('//fieldset//dl//dt', 'Appareils autorisés');
-  }
-}
-
-
-
-
-class AlbumControllerDilicomPNBEditPostTest extends Admin_AlbumControllerDilicomPNBTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->postDispatch('/admin/album/edit_album/id/23',
-                        ['titre' => 'Tootem']);
-    Class_Album::clearCache();
-    Class_Album_Item::clearCache();
-    Class_Album_UsageConstraint::clearCache();
-  }
-
-
-  /** @test */
-  public function constraintsShouldNotHaveBeenDeleted() {
-    $item = Class_Album::find(23)->getItems()[0];
-    $this->assertNotNull($item->getUsageConstraints()->getLoanConstraint());
-  }
-}
-
-
-
-
-class AlbumControllerDilicomPNBImportDilicomPaginatorTest extends Admin_AlbumControllerDilicomPNBTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->generateDilicomAlbums();
-    $this->dispatch('admin/album/dilicom', true);
-  }
-
-
-  /** @test */
-  public function contextShouldExpectation() {
-    $this->assertXPathContentContains('//div[@class="pager model_table_pager"]', '1', $this->_response->getBody());
-  }
-
-
-  protected function generateDilicomAlbums() {
-    for($id=50; $id<=75; $id++) {
-     $this->fixture('Class_Album',
-                   ['id' => $id,
-                    'titre' => 'Being human n°' . $id,
-                    'type_doc_id' => Class_TypeDoc::DILICOM,
-                    'usage_constraints' =>
-                    [
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 1000 + $id,
-                                     'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 45,
-                                                                        Class_Album_UsageConstraint::QUANTITY => 30,
-                                                                        Class_Album_UsageConstraint::MAX_NB_OF_USERS => 15])]),
-
-                     $this->fixture('Class_Album_UsageConstraint',
-                                    ['id' => 100 + $id,
-                                     'usage_type' => Class_Album_UsageConstraint::DEVICE_SHARE_CONSTRAINT,
-                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::QUANTITY => 6])])
-                    ]
-                   ]);
-    }
-  }
-}
-
-
-
-
-class Admin_AlbumControllerDilicomPNBExportCsvTest extends Admin_AlbumControllerDilicomPNBTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    Class_Album_UsageConstraints::setTimeSource(new TimeSourceForTest('2015-12-04 14:14:14'));
-    $this->dispatch('admin/album/dilicom-export-csv', true);
-  }
-
-
-  public function tearDown() {
-    Class_Album_UsageConstraints::setTimeSource(null);
-    parent::tearDown();
-  }
-
-
-  /** @test */
-  public function filenameShouldBeDilicomCsv() {
-    $this->assertContains(['name' => 'Content-Type',
-                           'value' => 'text/csv; name="dilicom_csv.csv"',
-                           'replace' => true], $this->_response->getHeaders());
-  }
-
-
-  /** @test */
-  public function csvShouldContainsAlbumsItems() {
-    $this->assertEquals('Titre;"Prêts / Droits";"Nombre de prêts";"Prêts simultanés / Droits";"Prêts simultanés";"Durée de prêt en jours";"Nombre de jours restant sur la licence";"Date de commande";Auteur;Éditeur;Collection;Année;Genre;Section;Catégorie
-"Being human being";"10 / 30";10;"2 / 15";2;45;2095;30/03/2015;;;;;;;"Albums non classés"
-"Being human being";"20 / 20";20;"5 / 15";5;20;19902;29/08/2015;;;;;;;"Albums non classés"
-"Hell is from here to eternity";"2 / ∞";2;"1 / ∞";1;359;∞;30/03/2015;"Iron Maiden";EMI;"Temple Of Rock";1992;"Heavy Metal";"Espace métal";Fondu
-', $this->_response->getBody());
-  }
-}
-
-
-
-class Admin_AlbumControllerDilicomPNBExportLoansCsvTest extends Admin_AlbumControllerDilicomPNBTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    Class_Album_UsageConstraints::setTimeSource(new TimeSourceForTest('2015-12-04 14:14:14'));
-    $this->dispatch('admin/album/dilicom-export-loans-csv', true);
-  }
-
-
-  public function tearDown() {
-    Class_Album_UsageConstraints::setTimeSource(null);
-    parent::tearDown();
-  }
-
-
-  /** @test */
-  public function filenameShouldBeDilicomLoansCsv() {
-    $this->assertContains(['name' => 'Content-Type',
-                           'value' => 'text/csv; name="dilicom_loans_csv.csv"',
-                           'replace' => true],
-                          $this->_response->getHeaders());
-  }
-
-
-  /** @test */
-  public function csvShouldContainsAlbumsItems() {
-    $this->assertEquals('Date;Titre;"Date de commande";Auteur;Éditeur;Collection;Année;Genre;Section;Catégorie
-16/12/2016;"Hell is from here to eternity";30/03/2015;"Iron Maiden";EMI;"Temple Of Rock";1992;"Heavy Metal";"Espace métal";Fondu
-13/11/2017;"Hell is from here to eternity";30/03/2015;"Iron Maiden";EMI;"Temple Of Rock";1992;"Heavy Metal";"Espace métal";Fondu
-',
-                        $this->_response->getBody());
-  }
-}
\ No newline at end of file
diff --git a/tests/application/modules/admin/controllers/AlbumControllerTest.php b/tests/application/modules/admin/controllers/AlbumControllerTest.php
index 532ff3834db0516607cc1624b821d4ac5effb8e1..64821a20b22be6dcece4adb20178b04e35f8a1a1 100644
--- a/tests/application/modules/admin/controllers/AlbumControllerTest.php
+++ b/tests/application/modules/admin/controllers/AlbumControllerTest.php
@@ -2695,49 +2695,6 @@ class Admin_AlbumControllerImportEADTest extends Admin_AlbumControllerTestCase {
 
 
 
-
-class Admin_AlbumControllerImportDilicomTest extends Admin_AlbumControllerTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    RessourcesNumeriquesFixtures::activateDilicom();
-    $this->dispatch('/admin/album/dilicom', true);
-  }
-
-
-  /** @test */
-  public function titleShouldBeImportPnbDilicom() {
-    $this->assertXPathContentContains('//h1', 'PNB Dilicom');
-  }
-
-
-  /** @test */
-  public function importOffresPnbDilicomShouldBePresent() {
-    $this->assertXPathContentContains('//h2', 'Import des offres Dilicom/PNB');
-  }
-
-
-  /** @test */
-  public function usedRessourcesShouldBePresent() {
-    $this->assertXPathContentContains('//h2', 'Utilisation des ressources PNB Dilicom');
-  }
-
-
-  /** @test */
-  public function formImportOffersShouldContainsFileInputForXML() {
-    $this->assertXPath('//form[contains(@action, "admin/album/dilicom")]//input[@type="file"][@name="offers"]');
-  }
-
-
-  /** @test */
-  public function formShouldHaveSubmitButtonImportXML() {
-    $this->assertXPath('//input[@type="submit"][@value="Importer le fichier XML"]');
-  }
-}
-
-
-
-
 /** LL: quand j'aurais trouvé comment contourner is_uploaded_file */
 abstract class Admin_AlbumControllerPostImportEADTest extends Admin_AlbumControllerAlbumHarlockTestCase {
   public function setUp() {
diff --git a/tests/application/modules/admin/controllers/IndexControllerTest.php b/tests/application/modules/admin/controllers/IndexControllerTest.php
index 41b78f2023188bee10115ff4a111180019f29dd3..895333b89180b95b7d909f6023be3b3dd656efed 100644
--- a/tests/application/modules/admin/controllers/IndexControllerTest.php
+++ b/tests/application/modules/admin/controllers/IndexControllerTest.php
@@ -303,31 +303,6 @@ class Admin_IndexControllerClearCacheActionTest extends Admin_IndexControllerTes
 
 
 
-class Admin_IndexControllerDilicomTest extends Admin_IndexControllerTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->fixture('Class_AdminVar',
-                   ['id' => 'DILICOM_PNB_GLN_CONTRACTOR',
-                    'clef' => 'DILICOM_PNB_GLN_CONTRACTOR',
-                    'valeur' => 1]);
-
-    $this->fixture('Class_AdminVar',
-                   ['id' => 'DILICOM_PNB_SERVER_URL',
-                    'clef' => 'DILICOM_PNB_SERVER_URL',
-                    'valeur' => 1]);
-
-    $this->dispatch('/admin/index/index', true);
-  }
-
-/** @test */
-  public function PnbDilicomnShouldBePresent() {
-    $this->assertXPathContentContains($this->_bibnum_menu_path . '//a', 'PNB Dilicom');
-  }
-}
-
-
-
 class AdminIndexControllerWithRedmineTest extends Admin_AbstractControllerTestCase {
   protected $_storm_default_to_volatile = true;
 
diff --git a/tests/application/modules/opac/controllers/AbonneControllerPretsTest.php b/tests/application/modules/opac/controllers/AbonneControllerPretsTest.php
index df8400bdc87eb53a563a5734cf6eea8551a8b548..256138670eb140f7909d2b6196218fedda9c0643 100644
--- a/tests/application/modules/opac/controllers/AbonneControllerPretsTest.php
+++ b/tests/application/modules/opac/controllers/AbonneControllerPretsTest.php
@@ -76,7 +76,7 @@ abstract class AbstractAbonneControllerPretsTestCase extends AbstractControllerT
                         ->whenCalled('open_url')
                         ->answers('')
                         ->whenCalled('open_url')
-                        ->with('/v2/pnb-numerique/json/getEndedLoans?glnContractor=777&loanId[0]=5')
+                        ->with('/v3/pnb-numerique/json/getEndedLoans?login=test-gln&password=test-gln-pass&glnContractor=777&loanId%5B0%5D=5&loanId%5B1%5D=6')
                         ->answers(DilicomFixtures::getEndedLoansResponse())
       ;
 
@@ -486,7 +486,7 @@ class AbonneControllerPretsListThreePretsPnbEarlyReturnTest extends AbonneContro
 
     $this->_http
       ->whenCalled('open_url')
-      ->with('/v2/pnb-numerique/json/getEndedLoans?glnContractor=777&loanId[0]=5&loanId[1]=6',
+      ->with('/v3/pnb-numerique/json/getEndedLoans?login=test-gln&password=test-gln-pass&glnContractor=777&loanId%5B0%5D=5&loanId%5B1%5D=6',
              ['auth' => ['user' => 'test-gln',
                          'password' => 'test-gln-pass']])
       ->answers(DilicomFixtures::getEndedLoansResponse(['{"loanId":"5","returnDate":"2015-09-18T14:30:00+02:00"}']))
diff --git a/tests/application/modules/opac/controllers/BibNumeriqueControllerDilicomTest.php b/tests/application/modules/opac/controllers/BibNumeriqueControllerDilicomTest.php
deleted file mode 100644
index 938c3280ddef46667a444c147e4dc27079bd9dc5..0000000000000000000000000000000000000000
--- a/tests/application/modules/opac/controllers/BibNumeriqueControllerDilicomTest.php
+++ /dev/null
@@ -1,772 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012, 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
- */
-require_once 'tests/fixtures/DilicomFixtures.php';
-
-abstract class BibNumeriqueContollerDilicomTestCase extends AbstractControllerTestCase {
-  protected
-    $_http,
-    $_time_source,
-    $_storm_default_to_volatile = true;
-
-  public function setUp() {
-    parent::setUp();
-
-    $this->fixture('Class_Bib',
-                   ['id' => 1,
-                    'gln' => '2345889']);
-
-    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 0);
-
-    $logged_user = $this->fixture('Class_Users',
-                                  ['id' => 6,
-                                   'nom'=>'Pito',
-                                   'login'=>'Chat',
-                                   'password'=>'123456',
-                                   'id_site' => 1,
-                                   'idabon' => '12345',
-                                   'user_groups' => [$this->fixture('Class_UserGroup',
-                                                                    ['id' => '20',
-                                                                     'libelle' => 'Multimedia',
-                                                                     'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]])]]);
-    $logged_user->beAbonneSIGB()->assertSave();
-    ZendAfi_Auth::getInstance()->logUser($logged_user);
-
-    $this->book = $this->fixture('Class_Album',
-                                 ['id' => 3,
-                                  'titre' => 'Totem et Thora',
-                                  'id_origine' => 'Dilicom-88817216',
-                                  'external_uri' => 'http://www.edenlivres.fr/p/23416',
-                                  'type_doc_id' => Class_TypeDoc::DILICOM,
-                                  'isbn' => '435465',
-                                  'notice' => $this->fixture('Class_Notice', ['id' => 38]),
-                                  'items' => [$this->fixture('Class_Album_Item',
-                                                             ['id' => 1,
-                                                              'album_id' => 3,
-                                                              'loan_count' => 2,
-                                                              'quantity' => 4,
-                                                              'usage_constraints' => [$this->fixture('Class_Album_UsageConstraint',
-                                                                                                     ['id' => 1,
-                                                                                                      'album_id' => 3,
-                                                                                                      'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
-                                                                                                      Class_Album_UsageConstraint::MAX_NB_OF_USERS => 40,
-                                                                                                      Class_Album_Usageconstraint::QUANTITY => 50,
-                                                                                                      Class_Album_UsageConstraint::DURATION => '100']),
-                                                                                      $this->fixture('Class_Album_UsageConstraint',
-                                                                                                     ['id' => 2,
-                                                                                                      'album_id' => 3,
-                                                                                                      'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
-                                                                                                      Class_Album_UsageConstraint::ORDER_LINE_ID => 'x321',
-                                                                                                      Class_Album_UsageConstraint::DURATION => '100',
-                                                                                                      Class_Album_UsageConstraint::ORDER_DATE => '2015-04-01 00:00:00'])
-                                                              ]
-                                                             ]
-                                    )]
-                                 ]);
-
-    RessourcesNumeriquesFixtures::activateDilicom();
-
-
-    $this->_http = Storm_Test_ObjectWrapper::mock();
-    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getLoanStatus?glnContractor=123456789&orderLineId[0]=x321',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getLoanStatusResponse());
-
-
-    $this->_time_source = new TimeSourceForTest('2014-05-02 14:14:14');
-    Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource($this->_time_source);
-    Class_Album_UsageConstraint::setTimeSource($this->_time_source);
-    Class_Album_UsageConstraints::setTimeSource($this->_time_source);
-    Class_Loan_Pnb::setTimeSource($this->_time_source);
-  }
-
-
-  public function tearDown() {
-    Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource(null);
-    Class_Album_UsageConstraint::setTimeSource(null);
-    Class_Loan_Pnb::setTimeSource(null);
-
-    parent::tearDown();
-  }
-}
-
-
-
-
-class BibNumeriqueContollerDilicomAjaxPopupBookActionTest extends BibNumeriqueContollerDilicomTestCase {
-  protected $_storm_default_to_volatile = true;
-
-  /** @test */
-  public function consultBookShouldContainsLinkToOpenAjax() {
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/consultBook?glnContractor=123456789&orderLineId=x321&accessMedium=STREAMING&localization=IN_SITU&consultEndDate=2014-05-02T15%3A14%3A14%2B0200&ean13=435465&ipAddress=127.0.0.1&glnColl=afi-bib&loanerColl=2345889&loanId=n4y4nq63',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookResponse())
-      ->beStrict();
-
-    $this->dispatch('/bib-numerique/consult-book-ajax/id/3', true);
-    $this->assertXpathContentContains('//div[@class="popup-content"]//button[contains(@onclick, "/bib-numerique/consult-book-open-ajax/id/3")][@data-popup="true"][@class="bouton validate"]', 'Oui');
-
-    $this->assertXpathContentContains('//button[contains(@onclick, "window.location.href")]', 'Non');
-  }
-
-
-  /** @test */
-  public function popupConsultBookOpenShouldContainsLinkToOpenPnbUrl() {
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/consultBook?glnContractor=123456789&orderLineId=x321&accessMedium=STREAMING&localization=IN_SITU&consultEndDate=2014-05-02T15%3A14%3A14%2B0200&ean13=435465&ipAddress=127.0.0.1&glnColl=afi-bib&loanerColl=2345889&loanId=n4y4nq63',
-                   ['auth' => [ 'user' => 'afi-bib',
-                               'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookResponse())
-      ->beStrict();
-
-    $this->dispatch('/bib-numerique/consult-book-open-ajax/id/3', true);
-    $this->assertContains('<div class=\"popup-content\"><a href=\"https:\/\/pnb-dilicom.centprod.com\/v2\/\/link\/3025594195810\/LOAN\/WIKI001\/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do\" target=\"_blank\" class=\"button blue\" onclick=\"opacDialogClose();\">Lire en ligne<\/a><\/div>"',
-                          $this->_response->getBody());
-  }
-
-
-  /** @test */
-  public function popupLoanBookOpenAjaxWithPnbErrorShouldContainsScriptToReloadPage() {
-        $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/consultBook?glnContractor=123456789&orderLineId=x321&accessMedium=STREAMING&localization=IN_SITU&consultEndDate=2014-05-02T15%3A14%3A14%2B0200&ean13=435465&ipAddress=127.0.0.1&glnColl=afi-bib&loanerColl=2345889&loanId=n4y4nq63',
-                   ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookErrorResponse())
-      ->beStrict();
-
-    $this->dispatch('/bib-numerique/consult-book-open-ajax/id/3', true);
-    $this->assertContains('<script>location.href=\"\/recherche\/viewnotice\/id\/3\/render\/false\"<\/script>', $this->_response->getBody());
-  }
-
-
-  /** @test */
-  public function loanBookPopupShouldContainsQuestion() {
-    $this->dispatch('/bib-numerique/loan-book-ajax/id/3/render/popup', true);
-    $this->assertContains('<p>Êtes vous sûr de vouloir emprunter ce document ?</p>',
-                          json_decode($this->_response->getBody())->content);
-  }
-
-
-  /** @test */
-  public function loanBookPopupShouldContainsLinkToDownload() {
-    $this->dispatch('/bib-numerique/loan-book-ajax/id/3', true);
-    $this->assertXpathContentContains('//button[contains(@onclick, "/bib-numerique/download-loan-book-ajax/id/3")][@data-popup="true"][@class="bouton validate"]', 'Oui');
-  }
-
-
-  /** @test */
-  public function loanBookPopupShouldContainDefaultMessage() {
-    $this->dispatch('/bib-numerique/loan-book-ajax/id/3/render/popup', true);
-    $this->assertContains('Votre compte sera mis à jour dans un délai de 15 minutes après le retour anticipé du document.', json_decode($this->_response->getBody())->content);
-  }
-
-
-  /** @test */
-  public function loanBookPopupShouldContainDefinedMessage() {
-    $this->fixture('Class_AdminVar',
-                   ['id' => 'DILICOM_PNB_LOAN_WARNING_MESSAGE',
-                    'valeur' => 'Don\'t use this !'
-                   ]);
-    $this->dispatch('/bib-numerique/loan-book-ajax/id/3/render/popup', true);
-    $this->assertContains('Don\'t use this !', json_decode($this->_response->getBody())->content);
-  }
-
-
-  /** @test */
-  public function downloadLinkShouldAnswersDilicomLink() {
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/loanBook?glnContractor=123456789&orderLineId=x321&accessMedium=DOWNLOAD&glnColl=afi-bib&loanerColl=2345889&localization=EX_SITU&loanEndDate=2014-08-10T14%3A14%3A14%2B0200&ean13=435465&loanId=1',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookResponse())
-      ->beStrict();
-
-    $this->dispatch('bib-numerique/download-loan-book-ajax/id/3', true);
-    $this->assertContains('"<div class=\"popup-content\"><a href=\"https:\/\/pnb-dilicom.centprod.com\/v2\/\/link\/3025594195810\/LOAN\/WIKI001\/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do\" class=\"button blue\" onclick=\"opacDialogClose();\">T\u00e9l\u00e9charger<\/a><\/div>"', $this->_response->getBody());
-  }
-
-
-  /** @test */
-  public function downloadLinkWithPnbErrorShouldConstainsScriptToReloadPage() {
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/loanBook?glnContractor=123456789&orderLineId=x321&accessMedium=DOWNLOAD&glnColl=afi-bib&loanerColl=2345889&localization=EX_SITU&loanEndDate=2014-08-10T14%3A14%3A14%2B0200&ean13=435465&loanId=1',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookErrorResponse())
-      ->beStrict();
-
-    $this->dispatch('bib-numerique/download-loan-book-ajax/id/3', true);
-    $this->assertContains('<script>location.href=\"\/recherche\/viewnotice\/id\/3\/render\/false\"<\/script>', $this->_response->getBody());
-  }
-
-
-  /** @test */
-  public function downloadLinkWithNoConnectedUserShouldRenderLoginPopup() {
-    ZendAfi_Auth::getInstance()->clearIdentity();
-    $this->dispatch('bib-numerique/download-loan-book-ajax/id/3', true);
-    $this->assertContains('"title":"Authentification"', $this->_response->getBody());
-  }
-}
-
-
-
-
-class BibNumeriqueContollerDilicomConsultBookActionTest extends BibNumeriqueContollerDilicomTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/consultBook?glnContractor=123456789&orderLineId=x321'
-             .'&accessMedium=STREAMING'
-             .'&localization=IN_SITU'
-             .'&consultEndDate='.urlencode('2014-05-02T15:14:14+0200')
-             .'&ean13=435465'
-             .'&ipAddress=195.251.88.223'
-             .'&glnColl=afi-bib'
-             .'&loanerColl=2345889'
-             .'&loanId='.base_convert($this->_time_source->time(), 10, 36).'63',
-                   ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers('{"orderLineId":"54e7473f975a2fa6aa4d3e17","consultEndDate":"2014-05-02T15:14:14+0200","loanId":"3","returnStatus":"OK","returnMessage":[],"requestId":"awvzrcttestpnbv2_000000039_201503051511","link":{"url":"https://pnb-test.centprod.com/v2//link/3056000302801/CONSULT/3/9791023501766-FGR9FJJGJCMXYMB8BO87XR9TPHDN9QNS.do","aformatDescription":"EPUB","mimetype":"application/epub+zip","ean13":"9791023501766","format":"E101"}}')
-      ->beStrict();
-
-
-    $_SERVER['HTTP_X_FORWARDED_FOR'] = '195.251.88.223';
-
-    $this->dispatch('/bib-numerique/consult-book/id/3', true);
-  }
-
-
-  public function tearDown() {
-    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
-    RessourcesNumeriquesFixtures::deactivateDilicom();
-    parent::tearDown();
-  }
-
-
-  /** @test */
-  public function openUrlShouldHaveBeenCalled() {
-    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
-  }
-
-
-
-  /** @test */
-  public function responseShouldRedirectToConsultBookUrl() {
-    $this->assertRedirectTo('https://pnb-test.centprod.com/v2//link/3056000302801/CONSULT/3/9791023501766-FGR9FJJGJCMXYMB8BO87XR9TPHDN9QNS.do');
-  }
-}
-
-
-
-
-class BibNumeriqueContollerDilicomConsultBookWithErrorsActionTest extends BibNumeriqueContollerDilicomTestCase {
-  public function setUp() {
-    parent::setUp();
-
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->answers('{"orderLineId":"54e7473f975a2fa6aa4d3e17","consultEndDate":"","loanId":"3","returnStatus":"ERROR","returnMessage":["Crash boom"],"requestId":"awvzrcttestpnbv2_000000039_201503051511","link":{}}');
-
-    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
-
-    $this->dispatch('/bib-numerique/consult-book/id/3', true);
-  }
-
-
-  /** @test */
-  public function responseShouldRedirectToAlbumRecord38() {
-    $this->assertRedirectTo('/recherche/viewnotice/id/38', $this->getResponseLocation());
-  }
-
-
-  /** @test */
-  public function errorMessageShouldBeInNotifications() {
-    $this->assertFlashMessengerContentContains('Crash boom');
-  }
-}
-
-
-
-abstract class BibNumeriqueControllerDilicomLoanBookActionTestCase extends BibNumeriqueContollerDilicomTestCase {
-    public function setUp() {
-    parent::setUp();
-    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
-
-    Class_AdminVar::set('DILICOM_PNB_LOAN_COUNT_LIMIT', 0);
-
-    $update_status_url = 'https://pnb-test.centprod.com/v2/pnb-numerique/json/getLoanStatus?glnContractor=123456789&orderLineId[0]=x321';
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/loanBook?glnContractor=123456789&orderLineId=x321&accessMedium=DOWNLOAD&glnColl=afi-bib&loanerColl=2345889&localization=EX_SITU&loanEndDate=2014-08-10T14%3A14%3A14%2B0200&ean13=435465&loanId=1',
-                   ['auth' => [ 'user' => 'afi-bib',
-                               'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookResponse())
-
-
-      ->whenCalled('open_url')
-      ->with($update_status_url,
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->willDo(function() use ($update_status_url)
-               {
-                 $this->_http
-                   ->whenCalled('open_url')
-                   ->with($update_status_url,
-                          ['auth' => [ 'user' => 'afi-bib',
-                                      'password' => 'secretPassword']])
-                   ->answers(DilicomFixtures::getLoanStatusAfterLoanResponse());
-                 return DilicomFixtures::getLoanStatusResponse();
-               })
-      ->beStrict();
-    }
-}
-
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionTest extends BibNumeriqueControllerDilicomLoanBookActionTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-  }
-
-
-  /** @test */
-  public function responseShouldReturnDownloadUrl() {
-    $this->assertRedirectTo('https://pnb-dilicom.centprod.com/v2//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do');
-  }
-
-
-  /** @test */
-  public function bokehLoanShouldHaveBeenSaved() {
-    $this->assertNotNull(Class_Loan_Pnb::findFirstBy(['record_origin_id' => 'Dilicom-88817216']));
-  }
-
-
-  /** @test */
-  public function bokehLoanOrderLineIdShouldBe() {
-    $loan = Class_Loan_Pnb::findFirstBy(['record_origin_id' => 'Dilicom-88817216']);
-    $this->assertEquals('x321', $loan->getOrderLineId());
-  }
-
-
-  /** @test */
-  public function loanUrlShouldHaveBeenSaved() {
-    $this->assertEquals('https://pnb-dilicom.centprod.com/v2//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do', Class_Loan_Pnb::findFirstBy(['record_origin_id' => 'Dilicom-88817216'])->getLoanLink());
-  }
-
-
-  /** @test */
-  public function itemQuantityShouldEqualsTwentySeven() {
-    $this->assertEquals(27, $this->book->getItems()[0]->getQuantity());
-  }
-}
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionErrorsTest extends BibNumeriqueContollerDilicomTestCase {
-  public function setUp() {
-    parent::setUp();
-    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
-
-    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 10);
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/loanBook?glnContractor=123456789&orderLineId=x321&accessMedium=DOWNLOAD&glnColl=afi-bib&loanerColl=2345889&localization=EX_SITU&loanEndDate=2014-05-12T14%3A14%3A14%2B0200&ean13=435465&loanId=1',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookErrorResponse())
-      ->beStrict();
-
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-  }
-
-
-
-  /** @test */
-  public function responseShouldRedirectToAlbumNoticeId38() {
-    $this->assertRedirectTo('/recherche/viewnotice/id/38',
-                            $this->getResponseLocation());
-  }
-
-
-  /** @test */
-  public function errorMessageShouldBeInNotifications() {
-    $this->assertFlashMessengerContentContains('Are you trying to download this book ?');
-  }
-
-
-  /** @test */
-  public function loanShouldHaveBeedDeleted() {
-    $this->assertNull(Class_Loan_Pnb::find(1));
-  }
-}
-
-
-
-class BibNumeriqueControllerDilicomLoanBookWrongAlbumTest extends BibNumeriqueContollerDilicomTestCase {
-  /** @test */
-  public function responseShouldRedirectToReferer() {
-    $_SERVER['HTTP_REFERER'] = '/recherche/viewnotice/id/3789';
-    $this->dispatch('/bib-numerique/loan-book/id/32', true);
-    $this->assertRedirectTo('/recherche/viewnotice/id/3789');
-  }
-}
-
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionTwiceWithSameUserTest extends BibNumeriqueContollerDilicomTestCase {
-  public function setUp() {
-    parent::setUp();
-    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getEndedLoans?glnContractor=123456789&loanId[0]=5',
-                   ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getEndedLoansResponse())
-
-      ->beStrict();
-
-    $this->fixture('Class_Loan_Pnb',
-                   ['id' => 5,
-                    'user_id' => '6',
-                    'record_origin_id' => 'Dilicom-88817216',
-                    'expected_return_date' => '2014-05-02 18:14:14',
-                    'loan_link' => 'https://pnb-dilicom.centprod.com/v2//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do']);
-
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-  }
-
-
-  /** @test */
-  public function responseShouldReturnDownloadUrl() {
-    $this->assertRedirectTo('https://pnb-dilicom.centprod.com/v2//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do');
-  }
-}
-
-
-
-abstract class BibNumeriqueContollerDilicomSecondUserTestCase extends BibNumeriqueContollerDilicomTestCase {
-  public function setUp() {
-    parent::setUp();
-    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
-
-    $second_user = $this->fixture('Class_Users',
-                                  ['id' => 78,
-                                   'nom'=>'Dily',
-                                   'login'=>'Chaton',
-                                   'password'=>'123456',
-                                   'id_site' => 1,
-                                   'idabon' => '654321',
-                                   'user_groups' => [Class_UserGroup::find(20)]]);
-
-    $second_user->beAbonneSIGB()->assertSave();
-    ZendAfi_Auth::getInstance()->logUser($second_user);
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->never();
-  }
-}
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionWithASecondUserAndLoanCountExcededTest extends BibNumeriqueContollerDilicomSecondUserTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getLoanStatus?glnContractor=123456789&orderLineId[0]=x321',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getLoanStatusNoSimultaneousLoanLeftResponse())
-      ->beStrict();
-
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-  }
-
-
-  /** @test */
-  public function responseShouldRedirectToAlbumRecord38() {
-    $this->assertRedirectTo('/recherche/viewnotice/id/38');
-  }
-
-
-  /** @test */
-  public function notificationShouldReturnNoLoansAvailable() {
-    $this->assertFlashMessengerContentContains('Emprunt impossible. Le nombre d\'emprunts simultanés est atteint.');
-  }
-}
-
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionBookUnavailableTest extends BibNumeriqueContollerDilicomTestCase {
-  /** @test */
-  public function notificationShouldReturnNoLoansAvailableAfterAvailabilityEndDate() {
-    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 0);
-    $this->_time_source->setTime(strtotime('2015-07-10'));
-
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-
-    $this->assertFlashMessengerContentContains('Emprunt impossible. La ressource n\'est plus disponible');
-  }
-
-
-  /** @test */
-  public function notificationShouldReturnNoLoansAvailableWhenLoanDurationLessThanRemainingDays() {
-    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 5);
-    $this->_time_source->setTime(strtotime('2015-07-6'));
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-
-    $this->assertFlashMessengerContentContains('Emprunt impossible. La ressource n\'est plus disponible');
-  }
-}
-
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionWithTwoItemsTest extends BibNumeriqueContollerDilicomTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getLoanStatus?glnContractor=123456789&orderLineId[0]=x458',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getLoanStatusResponse());
-
-    $this->book->addItem(
-                         $this->fixture('Class_Album_Item',
-                                        ['id' => 2,
-                                         'loan_count' => 0,
-                                         'quantity' => 10,
-                                         'usage_constraints' => [$this->fixture('Class_Album_UsageConstraint',
-                                                                                ['id' => 4,
-                                                                                 'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
-                                                                                 Class_Album_UsageConstraint::MAX_NB_OF_USERS => 40,
-                                                                                 Class_Album_Usageconstraint::QUANTITY => 50,
-                                                                                 Class_Album_UsageConstraint::DURATION => 10]),
-                                                                 $this->fixture('Class_Album_UsageConstraint',
-                                                                                ['id' => 5,
-                                                                                 'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
-                                                                                 Class_Album_UsageConstraint::ORDER_LINE_ID => 'x458',
-                                                                                 Class_Album_UsageConstraint::DURATION => '100',
-                                                                                 Class_Album_UsageConstraint::ORDER_DATE => '2015-07-01 00:00:00'])
-                                         ]
-                                        ]
-                         ));
-  }
-
-
-  protected function _dispatchAndAssertSecondBookLoaned() {
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-    $this->assertRedirectTo('https://pnb-dilicom.centprod.com/v2//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do',
-                            $this->getResponseLocation());
-  }
-
-
-  /** @test */
-  public function withFirstItemExpiredShouldLoanSecondItem() {
-    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 0);
-    $this->_time_source->setTime(strtotime('2015-07-10'));
-
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/loanBook?glnContractor=123456789&orderLineId=x458&accessMedium=DOWNLOAD&glnColl=afi-bib&loanerColl=2345889&localization=EX_SITU&loanEndDate=2015-07-20T00%3A00%3A00%2B0200&ean13=435465&loanId=1',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookResponse());
-
-    $this->_dispatchAndAssertSecondBookLoaned();
-  }
-
-
-  /** @test */
-  public function withFirstItemLoandCountExcededShouldLoanSecondItem() {
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getLoanStatus?glnContractor=123456789&orderLineId[0]=x321',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getLoanStatusNoSimultaneousLoanLeftResponse())
-
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/loanBook?glnContractor=123456789&orderLineId=x458&accessMedium=DOWNLOAD&glnColl=afi-bib&loanerColl=2345889&localization=EX_SITU&loanEndDate=2014-05-12T14%3A14%3A14%2B0200&ean13=435465&loanId=1',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::loanBookResponse());
-
-    $this->_dispatchAndAssertSecondBookLoaned();
-  }
-
-
-
-  /** @test */
-  public function withSecondItemExpiredShouldReturnNoLoansAvailableAfterAvailabilityEndDate() {
-    $this->_time_source->setTime(strtotime('2016-07-10'));
-
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-
-    $this->assertFlashMessengerContentContains('Emprunt impossible. La ressource n\'est plus disponible');
-  }
-
-}
-
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionWithASecondUserAndGlobalLoanCountExcededTest extends BibNumeriqueContollerDilicomSecondUserTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    Class_AdminVar::set('DILICOM_PNB_LOAN_COUNT_LIMIT', 3);
-    Class_Album_Item::find(1)->setLoanCount(3)->save();
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-  }
-
-
-  /** @test */
-  public function responseShouldRedirectToAlbumRecord38() {
-    $this->assertRedirectTo('/recherche/viewnotice/id/38');
-  }
-
-
-  /** @test */
-  public function notificationShouldReturnNoLoansAvailable() {
-    $this->assertFlashMessengerContentContains('Emprunt impossible. Le nombre d\'emprunts simultanés est atteint.');
-  }
-}
-
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionUserLoanCountExceededTest extends BibNumeriqueContollerDilicomSecondUserTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->answers('');
-
-
-    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_PER_USER', 3);
-    foreach(range(1, 3) as $step)
-      $this->fixture('Class_Loan_Pnb',
-                     ['id' => $step,
-                      'user_id' => Class_Users::getIdentity()->getId(),
-                      'record_origin_id' => $step,
-                      'expected_return_date' => '2022-06-01']);
-
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-  }
-
-
-  /** @test */
-  public function responseShouldRedirectToAlbumRecord38() {
-    $this->assertRedirectTo('/recherche/viewnotice/id/38');
-  }
-
-
-  /** @test */
-  public function notificationShouldReturnNoLoansAvailable() {
-    $this->assertFlashMessengerContentContains('Emprunt impossible. Vous avez atteint votre quota de 3 emprunts.');
-  }
-}
-
-
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionWithASecondUserAndQuantityExcededTest extends BibNumeriqueContollerDilicomSecondUserTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getLoanStatus?glnContractor=123456789&orderLineId[0]=x321',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getLoanStatusNoQuantityLeftResponse());
-
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-  }
-
-
-
-
-  /** @test */
-  public function albumShouldHaveBeenUpdateThroughHubGetLoanStatus() {
-    $this->assertContains('getLoanStatus', $this->_http->getFirstAttributeForLastCallOn('open_url'));
-  }
-
-
-  /** @test */
-  public function responseShouldRedirectToAlbumRecord38() {
-    $this->assertRedirectTo('/recherche/viewnotice/id/38');
-  }
-
-
-  /** @test */
-  public function notificationShouldContainsNoLoansAvailable() {
-    $this->assertFlashMessengerContentContains('Emprunt impossible. Le nombre d\'emprunts disponible est épuisé.', $this->_getFlashMessengerMessages()[0]);
-  }
-}
-
-
-
-class BibNumeriqueControllerDilicomLoanBookActionWithASecondUserAndOfferValidityExcededTest extends BibNumeriqueContollerDilicomSecondUserTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->_time_source = new TimeSourceForTest('2018-05-02 14:14:14');
-    Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource($this->_time_source);
-    Class_Album_UsageConstraint::setTimeSource($this->_time_source);
-
-    $this->dispatch('/bib-numerique/loan-book/id/3', true);
-  }
-
-
-  /** @test */
-  public function notificationShouldContainsErrorMessage() {
-    $this->assertFlashMessengerContentContains('Emprunt impossible. L\'emprunt du document n\'est plus disponible.', $this->_getFlashMessengerMessages()[0]);
-  }
-}
diff --git a/tests/application/modules/opac/controllers/RechercheControllerViewnoticeTest.php b/tests/application/modules/opac/controllers/RechercheControllerViewnoticeTest.php
index a382cf40781ebb1e466ae95ae18f56c2e224afe7..bb8096623d24124a0ff4fe23f900c0505af9e90b 100644
--- a/tests/application/modules/opac/controllers/RechercheControllerViewnoticeTest.php
+++ b/tests/application/modules/opac/controllers/RechercheControllerViewnoticeTest.php
@@ -44,39 +44,6 @@ class RechercheControllerViewnoticeWithInspectorGadgetTest extends AbstractContr
 
 
 
-
-class RechercheControllerViewnoticeDilicomWithInspectorGadgetTest extends Admin_AbstractControllerTestCase {
-  protected $_storm_default_to_volatile = true;
-
-  public function setUp() {
-    parent::setUp();
-
-    $this->fixture('Class_Notice',
-                   ['id' => 2,
-                    'type_doc' => Class_TypeDoc::DILICOM,
-                    'unimarc' => '',
-                    'alpha_titre' => '',
-                    'alpha_auteur' => '']);
-
-    $this->dispatch('/opac/recherche/viewnotice/id/2/inspector_gadget/1', true);
-  }
-
-
-  /** @test */
-  public function buttonNoticeBokehShouldBePresent() {
-    $this->assertXPathContentContains('//button', 'Notice Bokeh');
-  }
-
-
-  /** @test */
-  public function buttonDilicomShouldBePresent() {
-    $this->assertXPathContentContains('//button', 'Dilicom');
-  }
-}
-
-
-
-
 class RechercheControllerSearchResultWithInspectorGadgetTest extends AbstractControllerTestCase {
   protected $_storm_default_to_volatile = true;
 
diff --git a/tests/application/modules/telephone/controllers/AbonneControllerTest.php b/tests/application/modules/telephone/controllers/AbonneControllerTest.php
index 015a5b656ad6805010361e412104b0eac5b3ad54..ea3a4e26b70460df8ce5610b47f6198707d383c7 100644
--- a/tests/application/modules/telephone/controllers/AbonneControllerTest.php
+++ b/tests/application/modules/telephone/controllers/AbonneControllerTest.php
@@ -192,7 +192,7 @@ class AbonneControllerTelephoneFicheWithPNBTest extends AbonneControllerTelephon
 
     $http_client = $this->mock()
                         ->whenCalled('open_url')
-                        ->with('/v2/pnb-numerique/json/getEndedLoans?glnContractor=&loanId[0]=42',
+                        ->with('/v3/pnb-numerique/json/getEndedLoans?loanId%5B0%5D=42',
                                ['auth' => [ 'user' => null,
                                            'password' => null]])
                         ->answers('')
diff --git a/tests/application/modules/telephone/controllers/BibNumeriqueControllerTest.php b/tests/application/modules/telephone/controllers/BibNumeriqueControllerTest.php
index 0e924d501e69a865e01b027c98edcbd9435b72d7..c43d06cee552b9ef6184a746d180d82fed0ce0a3 100644
--- a/tests/application/modules/telephone/controllers/BibNumeriqueControllerTest.php
+++ b/tests/application/modules/telephone/controllers/BibNumeriqueControllerTest.php
@@ -19,7 +19,6 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 require_once 'TelephoneAbstractControllerTestCase.php';
-require_once 'tests/application/modules/opac/controllers/BibNumeriqueControllerDilicomTest.php';
 
 abstract class BibNumeriqueControllerTelephoneTestCase extends TelephoneAbstractControllerTestCase {
   public function setUp() {
@@ -137,34 +136,4 @@ class BibNumeriqueControllerTelephoneViewAlbumMultiMedia extends TelephoneAbstra
   public function pageShouldContainLinkToIntroductionMp3PlayResource12() {
     $this->assertXPath('//a[contains(@data-src, "bib-numerique/play-ressource/id/12.mp3")]');
   }
-
-}
-
-
-
-class BibNumeriqueContollerDilicomMobileTest extends BibNumeriqueControllerDilicomLoanBookActionTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $_SERVER['HTTP_USER_AGENT'] = 'iPhone';
-    Class_Profil::getCurrentProfil()
-      ->beTelephone()
-      ->assertSave();
-  }
-
-
-  public function tearDown() {
-    unset($_SERVER['HTTP_USER_AGENT']);
-    parent::tearDown();
-  }
-
-
-  /** @test */
-  public function loanBookResoponseShouldContainsDownloadUrl() {
-    $this->dispatch('/telephone/bib-numerique/loan-book/id/3', true);
-    $this->assertRedirectTo('https://pnb-dilicom.centprod.com/v2//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do');
-  }
-}
-
-
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/application/modules/telephone/controllers/RechercheControllerRessourceNumeriqueTest.php b/tests/application/modules/telephone/controllers/RechercheControllerRessourceNumeriqueTest.php
index e5e099d3529116464c8c7f469f6b826e9ce580e8..3e14652063754088377803c818acbb7cb66fecd5 100644
--- a/tests/application/modules/telephone/controllers/RechercheControllerRessourceNumeriqueTest.php
+++ b/tests/application/modules/telephone/controllers/RechercheControllerRessourceNumeriqueTest.php
@@ -19,7 +19,6 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-require_once 'TelephoneAbstractControllerTestCase.php';
 
 abstract class Telephone_RechercheControllerRessourceNumeriqueTestCase extends TelephoneAbstractControllerTestCase {
   protected $_storm_default_to_volatile = true;
@@ -170,63 +169,4 @@ class Telephone_RechercheControllerRessourceNumeriqueElserViewNoticeTest
                                       'Accéder à la vidéo',
                                       $this->_response->getBody());
   }
-}
-
-
-
-
-class Telephone_RechercheControllerRessourceNumeriqueViewNoticePNBTest extends Telephone_RechercheControllerRessourceNumeriqueTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->fixture('Class_Album',
-                   ['id' => 1,
-                    'titre' => 'Hypnophonic',
-                    'visible' => true,
-                    'status' => Class_Album::STATUS_VALIDATED,
-                    'type_doc_id' => Class_TypeDoc::DILICOM])->index();
-
-    $logged_user = $this->fixture('Class_Users',
-                                  ['id' => 6,
-                                   'nom'=>'Pito',
-                                   'login'=>'Chat',
-                                   'password'=>'123456',
-                                   'id_site' => 1,
-                                   'idabon' => '12345',
-                                   'user_groups' => [$this->fixture('Class_UserGroup',
-                                                                    ['id' => '20',
-                                                                     'libelle' => 'Multimedia',
-                                                                     'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]])]]);
-    $logged_user->beAbonneSIGB()->assertSave();
-    ZendAfi_Auth::getInstance()->logUser($logged_user);
-  }
-
-
-  /** @test */
-  public function pageShouldContainsLinkToRessouresNumeriques() {
-    $this->dispatch('/telephone/recherche/viewnotice/id/1', true);
-
-    $this->assertXPathContentContains('//ul[contains(@class, "doctype_112")]//a',
-                                         'Accéder à la ressource',$this->_response->getBody());
-  }
-
-
-  /** @test */
-  public function loanBookActionShouldBeLoanBookNotAjax() {
-    $this->dispatch('/telephone/recherche/ressourcesnumeriques/id/1', true);
-    $this->assertXPath('//a[contains(@href, "bib-numerique/loan-book/id/1")]');
-  }
-
-
-  /** @test */
-  public function consultBookActionShouldNotBeVisiable() {
-    $this->dispatch('/telephone/recherche/ressourcesnumeriques/id/1', true);
-    $this->assertNotXPath('//a[contains(@href, "bib-numerique/consult-book/id/1")]');
-  }
-
-
-  /** @test */
-  public function previewIframeShouldNotBeDisplayed() {
-    $this->dispatch('/telephone/recherche/ressourcesnumeriques/id/1', true);
-    $this->assertNotXPath('//iframe');
-  }
 }
\ No newline at end of file
diff --git a/tests/application/modules/telephone/controllers/TelephoneAbstractControllerTestCase.php b/tests/application/modules/telephone/controllers/TelephoneAbstractControllerTestCase.php
index fff38f0f996e96209a6516dc3cf7a1d7936b44b0..62a89ff7f2efb60c2c6b8427065729fe3b9ad120 100644
--- a/tests/application/modules/telephone/controllers/TelephoneAbstractControllerTestCase.php
+++ b/tests/application/modules/telephone/controllers/TelephoneAbstractControllerTestCase.php
@@ -34,5 +34,4 @@ abstract class TelephoneAbstractControllerTestCase extends AbstractControllerTes
     unset($_SERVER['HTTP_USER_AGENT']);
     parent::tearDown();
   }
-}
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 4f0cc8f0f8a906b08278c924464e5c928e1ad827..8276cf8b9dabcde00628a8aef0b45a8f99e3cc63 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -97,4 +97,4 @@ require_once 'tests/application/modules/telephone/controllers/TelephoneAbstractC
 register_shutdown_function(function(){
   TestSpeedTrap::printSpeedTrappedTests();
 });
-?>
+?>
\ No newline at end of file
diff --git a/tests/fixtures/DilicomFixtures.php b/tests/fixtures/DilicomFixtures.php
index db1304b30ae08a387291f2601a84c727454de64e..7801186c9c8fcdd6e5fa190ad3d8f4c928dc1758 100644
--- a/tests/fixtures/DilicomFixtures.php
+++ b/tests/fixtures/DilicomFixtures.php
@@ -27,7 +27,7 @@ class DilicomFixtures {
     return '{
   "link" : {
     "format" : "E101",
-    "url" : "https://pnb-dilicom.centprod.com/v2//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do",
+    "url" : "https://pnb-dilicom.centprod.com/v3//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do",
     "ean13" : "9782021153057",
     "formatDescription" : "EPUB",
     "mimetype" : "application/epub+zip"
@@ -46,7 +46,7 @@ class DilicomFixtures {
     return '{
   "link" : {
     "format" : "E101",
-    "url" : "https://pnb-dilicom.centprod.com/v2//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do",
+    "url" : "https://pnb-dilicom.centprod.com/v3//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do",
     "ean13" : "9782021153057",
     "formatDescription" : "EPUB",
     "mimetype" : "application/epub+zip"
@@ -84,12 +84,12 @@ class DilicomFixtures {
   "date" : "2014-08-14T11:32:12.786+02:00",
   "returnStatus" : "OK",
   "returnMessage" : [ ],
-  "loanResponseLine" : [ {
+  "loanResponseLine":[{
     "returnStatus" : "OK",
     "orderLineId" : "537119f5975a1d3260801864",
     "nta" : 24,
-    "nus1" : 5,
-    "nus2" : 30,
+    "nus1":5,
+    "nus2":30,
     "beginDate" : "2014-05-12T20:59:03+02:00",
     "endDate" : "2015-05-12T20:59:03+02:00"
   } ]
diff --git a/tests/library/Class/Batch/diffusion_pnb_3056309900005_20150728T050431Z.xml b/tests/fixtures/diffusion_pnb_3056309900005_20150728T050431Z.xml
similarity index 100%
rename from tests/library/Class/Batch/diffusion_pnb_3056309900005_20150728T050431Z.xml
rename to tests/fixtures/diffusion_pnb_3056309900005_20150728T050431Z.xml
diff --git a/tests/library/Class/Batch/full_pnb_3056309900005_20150726T050431Z.xml b/tests/fixtures/full_pnb_3056309900005_20150726T050431Z.xml
similarity index 100%
rename from tests/library/Class/Batch/full_pnb_3056309900005_20150726T050431Z.xml
rename to tests/fixtures/full_pnb_3056309900005_20150726T050431Z.xml
diff --git a/tests/library/Class/WebService/Dilicom/fixtures/full_pnb_666_20150220T150017Z.xml b/tests/fixtures/full_pnb_666_20150220T150017Z.xml
similarity index 100%
rename from tests/library/Class/WebService/Dilicom/fixtures/full_pnb_666_20150220T150017Z.xml
rename to tests/fixtures/full_pnb_666_20150220T150017Z.xml
diff --git a/tests/library/Class/WebService/Dilicom/fixtures/full_pnb_decitres.xml b/tests/fixtures/full_pnb_decitres.xml
similarity index 100%
rename from tests/library/Class/WebService/Dilicom/fixtures/full_pnb_decitres.xml
rename to tests/fixtures/full_pnb_decitres.xml
diff --git a/tests/library/Class/WebService/Dilicom/fixtures/la_mer.xml b/tests/fixtures/la_mer.xml
similarity index 100%
rename from tests/library/Class/WebService/Dilicom/fixtures/la_mer.xml
rename to tests/fixtures/la_mer.xml
diff --git a/tests/library/Class/WebService/Dilicom/fixtures/partial_pnb_666_20150220T150017Z.xml b/tests/fixtures/partial_pnb_666_20150220T150017Z.xml
similarity index 100%
rename from tests/library/Class/WebService/Dilicom/fixtures/partial_pnb_666_20150220T150017Z.xml
rename to tests/fixtures/partial_pnb_666_20150220T150017Z.xml
diff --git a/tests/library/Class/WebService/Dilicom/fixtures/produits_terroir.xml b/tests/fixtures/produits_terroir.xml
similarity index 100%
rename from tests/library/Class/WebService/Dilicom/fixtures/produits_terroir.xml
rename to tests/fixtures/produits_terroir.xml
diff --git a/tests/library/Class/WebService/Dilicom/fixtures/riviere_esperance.xml b/tests/fixtures/riviere_esperance.xml
similarity index 100%
rename from tests/library/Class/WebService/Dilicom/fixtures/riviere_esperance.xml
rename to tests/fixtures/riviere_esperance.xml
diff --git a/tests/library/Class/Batch/DilicomTest.php b/tests/library/Class/Batch/DilicomTest.php
deleted file mode 100644
index bf4299056925d5bda3d859ea22cd143701ba52f8..0000000000000000000000000000000000000000
--- a/tests/library/Class/Batch/DilicomTest.php
+++ /dev/null
@@ -1,705 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012-2014, 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
- */
-
-require_once 'tests/fixtures/DilicomFixtures.php';
-
-abstract class Class_Batch_DilicomTestCase extends ModelTestCase {
-  protected
-    $_storm_default_to_volatile = true,
-    $_log = '',
-    $_debug_log = '';
-
-  public function setUp() {
-    parent::setUp();
-    Storm_Cache::beVolatile();
-  }
-
-  public function getLogger() {
-    return $this->logger = $this->mock()
-                                ->whenCalled('log')
-                                ->willDo(
-                                         function($message, $target = '') {
-                                           if ($target == 'debug') {
-                                             $this->_debug_log .= $message . "\n";
-                                             return;
-                                           }
-                                           $this->_log .= $message;
-                                         });
-  }
-
-
-  protected function _enableDilicom() {
-    RessourcesNumeriquesFixtures::activateDilicom();
-  }
-
-
-  protected function _prepareTotemThora() {
-    return (new DilicomFixtures())->albumTotemThora();
-  }
-}
-
-
-
-class Class_Batch_DilicomSimpleTest extends Class_Batch_DilicomTestCase {
-  protected $_batch, $_jobs;
-
-  public function setUp() {
-    parent::setUp();
-    $this->_jobs = $this->mock()
-                        ->whenCalled('run')->answers(null)
-                        ->whenCalled('setLogger')->with(null)->answers(null)
-                        ->beStrict();
-
-    $this->_batch = (new Class_Batch_Dilicom)
-      ->setJobs($this->_jobs);
-  }
-
-
-  /** @test */
-  public function labelShouldBePnbDilicom() {
-    $this->assertEquals('PNB Dilicom', $this->_batch->getLabel());
-  }
-
-
-  /** @test */
-  public function withoutVarsShouldNotBeEnabled() {
-    $this->assertFalse($this->_batch->isEnabled());
-  }
-
-
-  /** @test */
-  public function withVarsShouldBeEnabled() {
-    $this->_enableDilicom();
-    $this->assertTrue($this->_batch->isEnabled());
-  }
-
-
-  /** @test */
-  public function runShouldRunJobs() {
-    $this->_batch->run();
-    $this->assertTrue($this->_jobs->methodHasBeenCalled('run'));
-  }
-
-
-  /** @test */
-  public function settingLoggerShouldSetJobsLogger() {
-    $this->_batch->setLogger(null);
-    $this->assertTrue($this->_jobs->methodHasBeenCalled('setLogger'));
-  }
-}
-
-
-
-abstract class Class_Batch_DilicomJobOnixTestCase extends Class_Batch_DilicomTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->fixture('Class_AdminVar',
-                   ['id' => 'DILICOM_PNB_FTP_SERVER',
-                    'clef' => 'DILICOM_PNB_FTP_SERVER',
-                    'valeur' => 'pftp.centprod.com']);
-
-    $this->fixture('Class_AdminVar',
-                   ['id' => 'DILICOM_PNB_FTP_USER',
-                    'clef' => 'DILICOM_PNB_FTP_USER',
-                    'valeur' => '007']);
-
-    $this->fixture('Class_AdminVar',
-                   ['id' => 'DILICOM_PNB_FTP_PASS',
-                    'clef' => 'DILICOM_PNB_FTP_PASS',
-                    'valeur' => 'IGotZeLicense']);
-
-    $this->_prepareFixtures();
-
-
-    $file_system = $this->mock()
-                        ->whenCalled('file_get_contents')
-                        ->with(PATH_TEMP . 'dilicom/full_pnb_3056309900005_20150726T050431Z.xml')
-                        ->answers(file_get_contents(__DIR__ . '/full_pnb_3056309900005_20150726T050431Z.xml'))
-
-                        ->whenCalled('file_get_contents')
-                        ->answers(file_get_contents(__DIR__ . '/diffusion_pnb_3056309900005_20150728T050431Z.xml'))
-                        ->whenCalled('file_exists')
-                        ->with(PATH_TEMP . 'dilicom/')
-                        ->answers(true);
-
-    Class_Batch_DilicomJobOnix::setFileSystem($file_system);
-
-    Class_WebService_BibNumerique_RessourceNumerique::setLogger($this->getLogger());
-
-    (new Class_Batch_DilicomJobOnix(new Class_Batch_Dilicom))
-      ->setFtpClient($this->getFtpClient())
-      ->setLogger($this->getLogger())
-      ->run();
-  }
-
-
-  public function tearDown() {
-    Class_WebService_BibNumerique_RessourceNumerique::setLogger(null);
-    parent::tearDown();
-  }
-
-
-  protected function _prepareFixtures() { }
-
-
-  public function getFtpClient() {
-    return $this->mock();
-  }
-}
-
-
-
-class Class_Batch_Dilicom_JobOnixNoConnectionTest extends Class_Batch_DilicomJobOnixTestCase {
-  public function getFtpClient() {
-    return parent::getFtpClient()
-      ->whenCalled('connect')
-      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
-      ->answers(false)
-
-      ->beStrict();
-  }
-
-
-  /** @test */
-  public function noConnectionErrorShouldBeLogged() {
-    $this->assertContains('Impossible de se connecter au serveur pftp.centprod.com',
-                          $this->_log);
-  }
-}
-
-
-
-class Class_Batch_DilicomJobOnixNoLsTest extends Class_Batch_DilicomJobOnixTestCase {
-  public function getFtpClient() {
-    return parent::getFtpClient()
-      ->whenCalled('connect')
-      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
-      ->answers(true)
-      ->whenCalled('ls')
-      ->with('HUB/O/')
-      ->answers(false)
-
-      ->beStrict();
-  }
-
-
-  /** @test */
-  public function noLsErrorShouldBeLogged() {
-    $this->assertContains('Impossible de lister le contenu de HUB/O/',
-                          $this->_log);
-  }
-}
-
-
-
-class Class_Batch_DilicomJobOnixNoGetTest extends Class_Batch_DilicomJobOnixTestCase {
-  public function getFtpClient() {
-    return parent::getFtpClient()
-      ->whenCalled('connect')
-      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
-      ->answers(true)
-      ->whenCalled('ls')
-      ->with('HUB/O/')
-      ->answers(['full_pnb_3056309900005_20150726T050431Z.xml',
-                 'full_pnb_3056309900005_20150719T050431Z.xml',
-                 'diffusion_pnb_3056309900005_20150720T050431Z.xml',
-                 'diffusion_pnb_3056309900005_20150728T050431Z.xml',
-                 '007.xml'])
-
-      ->whenCalled('get')
-      ->with('HUB/O/full_pnb_3056309900005_20150726T050431Z.xml',
-             PATH_TEMP . 'dilicom/full_pnb_3056309900005_20150726T050431Z.xml')
-      ->answers(false)
-      ->beStrict();
-  }
-
-
-  /** @test */
-  public function noGetErrorShouldBeLogged() {
-    $this->assertContains('Impossible de télécharger le fichier HUB/O/full_pnb_3056309900005_20150726T050431Z.xml',
-                          $this->_log);
-  }
-}
-
-
-
-class Class_Batch_DilicomJobOnixWithFullTest extends Class_Batch_DilicomJobOnixTestCase {
-  public function getFtpClient() {
-    return parent::getFtpClient()
-      ->whenCalled('connect')
-      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
-      ->answers(true)
-
-      ->whenCalled('ls')
-      ->with('HUB/O/')
-      ->answers(['full_pnb_3056309900005_20150726T050431Z.xml',
-                 'full_pnb_3056309900005_20150719T050431Z.xml',
-                 'diffusion_pnb_3056309900005_20150720T050431Z.xml',
-                 'diffusion_pnb_3056309900005_20150728T050431Z.xml',
-                 '007.xml'])
-
-      ->whenCalled('get')
-      ->with('HUB/O/full_pnb_3056309900005_20150726T050431Z.xml',
-             PATH_TEMP . 'dilicom/full_pnb_3056309900005_20150726T050431Z.xml')
-      ->answers(true)
-
-      ->whenCalled('get')
-      ->with('HUB/O/diffusion_pnb_3056309900005_20150728T050431Z.xml',
-             PATH_TEMP . 'dilicom/diffusion_pnb_3056309900005_20150728T050431Z.xml')
-      ->answers(true)
-
-      ->beStrict();
-  }
-
-
-  protected function _prepareFixtures() {
-    $this->fixture('Class_Batch',
-                   ['id' => 12,
-                    'type' => Class_Batch_Dilicom::TYPE,
-                    'data' => '']);
-  }
-
-
-  /** @test */
-  public function shouldLog4FilesInDirectory() {
-    $this->assertContains('4 fichier(s) à traiter dans HUB/O/', $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldLogFullFileToProcess() {
-    $this->assertContains('Fichier total à traiter : full_pnb_3056309900005_20150726T050431Z.xml',
-                          $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldLogIncrementalFileToProcess() {
-    $this->assertContains('Fichiers incrémentaux à traiter : diffusion_pnb_3056309900005_20150728T050431Z.xml',
-                          $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldHaveAdded3Albums() {
-    $this->assertEquals(3, Class_Album::countBy(['type_doc_id' => Class_TypeDoc::DILICOM]));
-  }
-
-
-  /** @test */
-  public function modelDataShouldContainsLastValidFileName() {
-    $this->assertEquals('diffusion_pnb_3056309900005_20150728T050431Z.xml',
-                        (new Class_Batch_Dilicom)->getModel()->getData());
-  }
-
-
-  /** @test */
-  public function debugLogShouldContainsImportLaReineDesNeiges() {
-    $this->assertContains('[Dilicom_Book] create "La Reine des Neiges" (Dilicom-9782290123409)',
-                          $this->_debug_log);
-
-    $this->assertContains('new album id: 1',
-                          $this->_debug_log);
-  }
-
-
-  /** @test */
-  public function dilicomItemIdOrigineShouldBeDilicom9782290123409() {
-    Class_Album::find(1)->index();
-    $this->assertNotNull(Class_Exemplaire::findFirstBy(['id_origine' => 'Dilicom-9782290123409']));
-    $this->assertNotNull(Class_Album::find(1)->getNotice());
-    $this->assertEquals('Dilicom-9782290123409', Class_Notice::find(1)->getAlbum()->getIdOrigine());
-  }
-}
-
-
-
-class Class_Batch_DilicomJobOnixWithDataTest extends Class_Batch_DilicomJobOnixTestCase {
-  public function getFtpClient() {
-    return parent::getFtpClient()
-      ->whenCalled('connect')
-      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
-      ->answers(true)
-
-      ->whenCalled('ls')
-      ->with('HUB/O/')
-      ->answers(['full_pnb_3056309900005_20150726T050431Z.xml',
-                 'full_pnb_3056309900005_20150719T050431Z.xml',
-                 'diffusion_pnb_3056309900005_20150720T050431Z.xml',
-                 'diffusion_pnb_3056309900005_20150728T050431Z.xml',
-                 '007.xml'])
-
-      ->whenCalled('get')
-      ->with('HUB/O/full_pnb_3056309900005_20150726T050431Z.xml',
-             PATH_TEMP . 'dilicom/full_pnb_3056309900005_20150726T050431Z.xml')
-      ->answers(true)
-
-      ->whenCalled('get')
-      ->with('HUB/O/diffusion_pnb_3056309900005_20150728T050431Z.xml',
-             PATH_TEMP . 'dilicom/diffusion_pnb_3056309900005_20150728T050431Z.xml')
-      ->answers(true)
-
-      ->beStrict();
-  }
-
-
-  protected function _prepareFixtures() {
-    $this->fixture('Class_Batch',
-                   ['id' => 12,
-                    'type' => Class_Batch_Dilicom::TYPE,
-                    'data' => 'full_pnb_3056309900005_20150822T050431Z.xml']);
-  }
-
-
-  /** @test */
-  public function shouldLogFullFileToProcess() {
-    $this->assertContains('Aucun fichier plus récent que le dernier fichier déjà traité (full_pnb_3056309900005_20150822T050431Z.xml)',
-                          $this->_log);
-  }
-}
-
-
-
-class Class_Batch_DilicomJobOnixWithoutFullTest extends Class_Batch_DilicomJobOnixTestCase {
-  public function getFtpClient() {
-    return parent::getFtpClient()
-      ->whenCalled('connect')
-      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
-      ->answers(true)
-
-      ->whenCalled('ls')
-      ->with('HUB/O/')
-      ->answers(['diffusion_pnb_3056309900005_20150720T050431Z.xml',
-                 'diffusion_pnb_3056309900005_20150728T050431Z.xml',
-                 '007.xml'])
-
-      ->whenCalled('get')
-      ->with('HUB/O/diffusion_pnb_3056309900005_20150720T050431Z.xml',
-             PATH_TEMP . 'dilicom/diffusion_pnb_3056309900005_20150720T050431Z.xml')
-      ->answers(true)
-
-      ->whenCalled('get')
-      ->with('HUB/O/diffusion_pnb_3056309900005_20150728T050431Z.xml',
-             PATH_TEMP . 'dilicom/diffusion_pnb_3056309900005_20150728T050431Z.xml')
-      ->answers(true)
-
-      ->beStrict();
-  }
-
-
-  /** @test */
-  public function shouldLog2FilesInDirectory() {
-    $this->assertContains('2 fichier(s) à traiter dans HUB/O/', $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldNotLogFullFileToProcess() {
-    $this->assertNotContains('Fichier total', $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldLogIncrementalFileToProcess() {
-    $this->assertContains('Fichiers incrémentaux à traiter : diffusion_pnb_3056309900005_20150720T050431Z.xml diffusion_pnb_3056309900005_20150728T050431Z.xml',
-                          $this->_log);
-  }
-}
-
-
-
-abstract class Class_Batch_DilicomJobEndedLoansTestCase
-  extends Class_Batch_DilicomTestCase {
-
-  protected $_batch, $_http, $_time_source;
-
-  public function setUp() {
-    parent::setUp();
-
-    $this->_http = $this->mock()->beStrict();
-    $this->_http
-      ->whenCalled('setAuth')->with('afi-bib', 'secretPassword')->answers(null)
-      ->whenCalled('setAuth')->with(null)->answers(null);
-    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
-
-    $this->_time_source = new TimeSourceForTest('2015-10-01T18:06:18+0200');
-    Class_Loan_Pnb::setTimeSource($this->_time_source);
-
-    $this->_enableDilicom();
-
-    $this->_batch = (new Class_Batch_DilicomJobEndedLoans(new Class_Batch_Dilicom))
-      ->setLogger($this->getLogger())
-      ->setMemoryCleaner(function() {});
-  }
-
-
-  public function tearDown() {
-    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
-    Class_Loan_Pnb::setTimeSource(null);
-    parent::tearDown();
-  }
-
-
-  protected function _loadLoans() {
-    $loan = $this->fixture('Class_Loan_Pnb',
-                           ['id' => 12,
-                            'expected_return_date' => '2015-12-01 17:33:33']);
-
-    $this
-      ->onLoaderOfModel('Class_Loan_Pnb')
-      ->whenCalled('findAllBy')
-      ->with(['where' => '(id > 0) and expected_return_date > "2015-10-01 18:06:18"',
-              'limit' => 200,
-              'order' => 'id'])
-      ->willDo(function() use ($loan) { return [$loan]; })
-
-      ->whenCalled('findAllBy')
-      ->with(['where' => '(id > 12) and expected_return_date > "2015-10-01 18:06:18"',
-              'limit' => 200,
-              'order' => 'id'])
-      ->willDo(function() { return []; })
-
-      ->whenCalled('findAllBy')
-      ->willDo(
-               function() {
-                 $this->fail('Unexpected call to Class_Loan_Pnb::findAllBy() with params :' . json_encode(func_get_args()). ')');
-               })
-      ;
-  }
-}
-
-
-
-class Class_Batch_DilicomJobEndedLoansDisabledTest
-  extends Class_Batch_DilicomJobEndedLoansTestCase {
-
-  protected function _enableDilicom() {}
-
-  public function setUp() {
-    parent::setUp();
-    $this->_batch->run();
-  }
-
-
-  /** @test */
-  public function shouldLogJobName() {
-    $this->assertContains('Vérification des prêts rendus de manière anticipée',
-                          $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldLogDisabledDilicom() {
-    $this->assertContains('PNB Dilicom désactivé', $this->_log);
-  }
-}
-
-
-
-
-class Class_Batch_DilicomJobEndedLoansNoLoansTest
-  extends Class_Batch_DilicomJobEndedLoansTestCase {
-
-  public function setUp() {
-    parent::setUp();
-    $this->_batch->run();
-  }
-
-
-  /** @test */
-  public function shouldLogNothingToDo() {
-    $this->assertContains('Aucun prêt à vérifier', $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldNotLogUpdatedLoans() {
-    $this->assertNotContains('Aucun prêt rendu de manière anticipée', $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldLogNotCallDilicomHub() {
-    $this->assertFalse($this->_http->methodHasBeenCalled('open_url'));
-  }
-}
-
-
-
-class Class_Batch_DilicomJobEndedLoansNoUpdatesTest
-  extends Class_Batch_DilicomJobEndedLoansTestCase {
-
-  public function setUp() {
-    parent::setUp();
-
-    Storm_Cache::beVolatile();
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getEndedLoans?glnContractor=123456789&loanId[0]=12',
-                             ['auth' => [ 'user' => 'afi-bib',
-                                   'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getEndedLoansResponse());
-
-    $this->_loadLoans();
-    $this->_batch->run();
-  }
-
-
-  /** @test */
-  public function shouldLogOneLoanToCheck() {
-    $this->assertContains('1 prêt(s) à vérifier', $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldLogNoUpdate() {
-    $this->assertContains('Aucun prêt rendu de manière anticipée', $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldCallDilicomHub() {
-    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
-  }
-}
-
-
-
-
-class Class_Batch_DilicomJobEndedLoansWithUpdatesTest
-  extends Class_Batch_DilicomJobEndedLoansTestCase {
-
-  protected $_early_returned = '2015-09-01T17:33:33+02:00';
-
-  public function setUp() {
-    parent::setUp();
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getEndedLoans?glnContractor=123456789&loanId[0]=12',
-             ['auth' => ['user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getEndedLoansResponse(['{"loanId":"12","returnDate":"'. $this->_early_returned . '"}']));
-
-
-    $this->_loadLoans();
-    $this->_batch->run();
-  }
-
-
-  /** @test */
-  public function shouldLogOneLoanToCheck() {
-    $this->assertContains('1 prêt(s) à vérifier', $this->_log);
-  }
-
-
-  /** @test */
-  public function shouldCallDilicomHub() {
-    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
-  }
-
-
-  /** @test */
-  public function shouldLogLoanUpdated() {
-    $this->assertContains('1 prêt(s) rendu(s) de manière anticipée', $this->_log);
-  }
-
-
-  /** @test */
-  public function loanReturnDateShouldBeUpdated() {
-    $this->assertEquals($this->_early_returned,
-                        Class_Loan_Pnb::find(12)->getExpectedReturnDate());
-  }
-
-
-  /** @test */
-  public function zone995ShouldContainsDateAndCommandOrderAndBarcod() {
-    $book = $this->_prepareTotemThora()
-      ->setVisible(true)
-      ->setStatus(Class_Album::STATUS_VALIDATED);
-
-    $book->save();
-    $book->index();
-
-    $item = ($items = Class_Notice::find(1)->getExemplaires())
-      ? array_pop($items)
-      : new Class_Exemplaire();
-
-    $this->assertEquals('a:5:{i:0;a:2:{s:4:"code";s:1:"a";s:6:"valeur";s:0:"";}i:1;a:2:{s:4:"code";s:1:"c";s:6:"valeur";s:19:"2015-04-01 00:00:00";}i:2;a:2:{s:4:"code";s:1:"f";s:6:"valeur";s:16:"Dilicom-88817216";}i:3;a:2:{s:4:"code";s:1:"n";s:6:"valeur";s:4:"x321";}i:4;a:2:{s:4:"code";s:1:"v";s:6:"valeur";s:26:"A consulter sur le portail";}}', $item->getZone995());
-  }
-}
-
-
-
-
-class Class_Batch_DilicomJobUnindexExpiredOrdersTest extends Class_Batch_DilicomTestCase{
-  protected
-    $_batch;
-
-  public function setUp() {
-    parent::setUp();
-
-    $this->_time_source = new TimeSourceForTest('2015-10-02 14:14:14');
-    Class_Album_UsageConstraint::setTimeSource($this->_time_source);
-    Class_Album_UsageConstraints::setTimeSource($this->_time_source);
-
-    $book = $this->_prepareTotemThora();
-    $book
-      ->getItems()[0]
-      ->getUsageConstraints()
-      ->getAvailabilityConstraint()
-      ->setDuration(1);
-
-    $book
-      ->setVisible(true)
-      ->save();
-    $book->index();
-
-    $this->_enableDilicom();
-
-    $this->_batch = (new Class_Batch_DilicomJobUnindexExpiredOrders(new Class_Batch_Dilicom));
-
-    $this->_batch
-      ->setLogger($this->getLogger())
-      ->run();
-  }
-
-
-  /** @test */
-  public function albumShouldNotBeVisible() {
-    $this->assertFalse(Class_Album::find(3)->getVisible());
-  }
-
-
-  /** @test */
-  public function loggerShouldContainsVerificationDesCommandesExpirees() {
-    $this->assertContains('Vérification des commandes expirées',
-                          $this->_log);
-  }
-
-
-  /** @test */
-  public function albumShouldBeIndexed() {
-    $this->assertEmpty(Class_Notice::findAll());
-  }
-}
\ No newline at end of file
diff --git a/tests/library/Class/NoticeOAITest.php b/tests/library/Class/NoticeOAITest.php
index 76d765ccb3ffb8c97e668bc1f309eb47ab01e8c5..50ecbfc16b820406c06c94b442726bd22faac776 100644
--- a/tests/library/Class/NoticeOAITest.php
+++ b/tests/library/Class/NoticeOAITest.php
@@ -126,8 +126,10 @@ class OAINoticeTestHarverstWithOneRecord extends PHPUnit_Framework_TestCase {
 
 
 
-class OAINoticeTestResume extends PHPUnit_Framework_TestCase {
+class OAINoticeTestResume extends ModelTestCase {
+
   public function setUp() {
+    parent::setup();
     $this->noticeOAI = new Class_NoticeOAI();
     $this->entrepot = new Class_EntrepotOAI();
     $this->entrepot
@@ -135,68 +137,54 @@ class OAINoticeTestResume extends PHPUnit_Framework_TestCase {
       ->setLibelle('BNF gallica')
       ->setHandler('http://oai.bnf.fr/oai2/OAIHandler');
 
-    $this->oai_service = $this->createMock('Class_WebServiceOAI',
-                                        array('getRecordsFromSet',
-                                              'setOAIHandler',
-                                              'hasNextRecords',
-                                              'getNextRecords',
-                                              'getListRecordsResumptionToken',
-                                              'setListRecordsResumptionToken'));
-    $this->oai_service
-      ->expects($this->once())
-      ->method('setOAIHandler')
-      ->with($this->equalTo('http://oai.bnf.fr/oai2/OAIHandler'))
-      ->will($this->returnValue($this->oai_service));
-
+    $this->oai_service = $this->mock();
+    $this->oai_service->beStrict();
     $this->noticeOAI->setOAIService($this->oai_service);
   }
 
-  public function testResumptionWhenNoRecordsRemaining() {
-    $token = new Class_WebService_ResumptionToken();
 
+  /** @test */
+  public function resumptionWhenNoRecordsRemaining() {
+    $token = new Class_WebService_ResumptionToken();
     $this->oai_service
-      ->expects($this->once())
-      ->method('setListRecordsResumptionToken')
-      ->will($this->returnValue($this->oai_service));
+      ->whenCalled('setOAIHandler')
+      ->with('http://oai.bnf.fr/oai2/OAIHandler')
+      ->answers($this->oai_service)
 
-    $this->oai_service
-      ->expects($this->once())
-      ->method('hasNextRecords')
-      ->will($this->returnValue(false));
+      ->whenCalled('setListRecordsResumptionToken')
+      ->with($token)
+      ->answers($this->oai_service)
+
+      ->whenCalled('hasNextRecords')
+      ->answers(false);
 
     $this->assertEquals(null,
                         $this->noticeOAI->resumeHarvest($this->entrepot, $token));
   }
 
 
-  public function testResumptionWhenRecordsRemaining() {
+  /** @test */
+  public function resumptionWhenRecordsRemaining() {
     $token = new Class_WebService_ResumptionToken();
+    $this->oai_service
+      ->whenCalled('setOAIHandler')
+      ->with('http://oai.bnf.fr/oai2/OAIHandler')
+      ->answers($this->oai_service)
 
-    $this->oai_service->
-      expects($this->once())
-      ->method('setListRecordsResumptionToken')
-      ->will($this->returnValue($this->oai_service));
+      ->whenCalled('setListRecordsResumptionToken')
+      ->with($token)
+      ->answers($this->oai_service)
 
-    $this->oai_service
-      ->expects($this->once())
-      ->method('hasNextRecords')
-      ->will($this->returnValue(true));
+      ->whenCalled('hasNextRecords')
+      ->answers(true)
 
-    $this->oai_service
-      ->expects($this->once())
-      ->method('getNextRecords')
-      ->will($this->returnValue(array()));
+      ->whenCalled('getNextRecords')
+      ->answers([])
 
-    $token = new Class_WebService_ResumptionToken();
-    $this->oai_service
-      ->expects($this->once())
-      ->method('getListRecordsResumptionToken')
-      ->will($this->returnValue($token));
+      ->whenCalled('getListRecordsResumptionToken')
+      ->answers($token);
 
     $this->assertEquals($token,
                         $this->noticeOAI->resumeHarvest($this->entrepot, $token));
   }
-}
-
-
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/library/Class/UsersTest.php b/tests/library/Class/UsersTest.php
index 12c8bca049867f68361c3e518e2d267827d99b93..cf88fb197a4178d10983f100e30b2d8b585867a9 100644
--- a/tests/library/Class/UsersTest.php
+++ b/tests/library/Class/UsersTest.php
@@ -187,114 +187,80 @@ class UsersFindByIdTest extends ModelTestCase {
 
 class UsersSaveTest extends ModelTestCase {
 
-  public function setUp() {
-    parent::setUp();
+  protected $_storm_default_to_volatile = true;
+
 
-    $formatDate = new Class_Multimedia_Utils_DateTimeFormat();
-    $formatDate->setCurrentDate('2013-10-23 11:47:38');
-    Class_Multimedia_Utils_DateTimeFormat::setInstance($formatDate);
-    $this->tbl_users = $this->_buildTableMock('Class_Users', array('insert', 'update', 'select', 'fetchAll'));
-    $this->tbl_users
-      ->expects($this->any())
-      ->method('select')
-      ->will($this->returnValue(new Zend_Db_Table_Select(new Storm_Model_Table(array('name' => 'bib_admin_users')))));
-
-    $this->tbl_users
-      ->expects($this->any())
-      ->method('fetchAll')
-      ->will($this->returnValue(new Zend_Db_Table_Rowset(array('data' => array()))));
-  }
-
-
-  public function testSaveNewUser() {
-    $this->tbl_users
-      ->expects($this->once())
-      ->method('insert')
-      ->with(['nom' => 'Coltrane',
-              'prenom' => 'John',
-              'login' => 'jcoltrane',
-              'password' => 'giantsteps',
-              'role_level' => 2,
-              'id_site' => 1,
-              'role' => 'abonne_sigb',
-              'idabon' => '1234',
-              'date_fin' => '',
-              'naissance' => '',
-              'date_debut' => '',
-              'telephone' => '',
-              'mail' => '',
-              'adresse' => '',
-              'code_postal' => '',
-              'ville' => '',
-              'id_sigb' => null,
-              'is_contact_sms' => 0,
-              'is_contact_mail' => 0,
-              'date_maj' => '2013-10-23 11:47:38',
-              'mobile' => '',
-              'civilite' => 0,
-              'ordreabon' => '',
-              'id_panier_courant' => 0,
-              'pseudo' => '',
-              'settings' => '',
-              'statut' => 0]);
-
-    Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Newsletter')
-      ->whenCalled('findAllBy')
-      ->answers([]);
-
-    $coltrane = new Class_Users();
-    $coltrane
-      ->setNom('Coltrane')
-      ->setPrenom('John')
-      ->setLogin('jcoltrane')
-      ->setPassword('giantsteps')
-      ->setRoleLevel(2)
-      ->setIdSite(1)
-      ->setRole('abonne_sigb')
-      ->setIdabon('1234')
-      ->setWrongAttribute(['ceci ne doit pas être sauvegardé'])
-      ->save();
-  }
-
-
-  public function testSaveExistingUser() {
-    $this->tbl_users
-      ->expects($this->once())
-      ->method('update')
-      ->with(['nom' => 'Truffaz',
-              'prenom' => 'Erik',
-              'login' => 'etruffaz',
-              'role' => 'invite',
-              'role_level' => 0,
-              'password' => 'nafnaf',
-              'id_site' => 1,
-              'mail' => 'erik@truffaz.com',
-              'id_user' => 34,
-              'date_fin' => '2001-10-23',
-              'idabon' => '',
-              'naissance' => '',
-              'date_debut' => '',
-              'telephone' => '',
-              'adresse' => '',
-              'code_postal' => '',
-              'ville' => '',
-              'id_sigb' => null,
-              'is_contact_sms' => 0,
-              'is_contact_mail' => 0,
-              'date_maj' => '2013-10-23 11:47:38',
-              'mobile' => '',
-              'civilite' => 0,
-              'ordreabon' => '',
-              'id_panier_courant' => 0,
-              'pseudo' => '',
-              'settings' => '',
-              'statut' => 0],
-             'id_user=\'34\'');
-
-    Class_Users::getLoader()
-      ->newFromRow(UserFixtures::truffaz())
-      ->setMail('erik@truffaz.com')
-      ->save();
+  /** @test */
+  public function saveNewUser() {
+    $new_user = Class_Users::newInstance(['nom' => 'Coltrane',
+                                          'prenom' => 'John',
+                                          'login' => 'jcoltrane',
+                                          'password' => 'giantsteps',
+                                          'role_level' => 2,
+                                          'id_site' => 1,
+                                          'role' => 'abonne_sigb',
+                                          'idabon' => '1234',
+                                          'date_fin' => '',
+                                          'naissance' => '',
+                                          'date_debut' => '',
+                                          'telephone' => '',
+                                          'mail' => '',
+                                          'adresse' => '',
+                                          'code_postal' => '',
+                                          'ville' => '',
+                                          'id_sigb' => null,
+                                          'is_contact_sms' => 0,
+                                          'is_contact_mail' => 0,
+                                          'date_maj' => '2013-10-23 11:47:38',
+                                          'mobile' => '',
+                                          'civilite' => 0,
+                                          'ordreabon' => '',
+                                          'id_panier_courant' => 0,
+                                          'pseudo' => '',
+                                          'settings' => '',
+                                          'statut' => 0]);
+
+    $new_user->save();
+    $this->assertEquals(1, $new_user->getId());
+    $this->assertEquals(1234, $new_user->getIdabon());
+  }
+
+
+  /** @test */
+  public function saveExistingUser() {
+    $user = $this->fixture('Class_Users',
+                           ['id' => 189,
+                            'nom' => 'Truffaz',
+                            'prenom' => 'Erik',
+                            'login' => 'etruffaz',
+                            'role' => 'invite',
+                            'role_level' => 0,
+                            'password' => 'nafnaf',
+                            'id_site' => 1,
+                            'mail' => 'erik@truffaz.com',
+                            'id_user' => 34,
+                            'date_fin' => '2001-10-23',
+                            'idabon' => '',
+                            'naissance' => '',
+                            'date_debut' => '',
+                            'telephone' => '',
+                            'adresse' => '',
+                            'code_postal' => '',
+                            'ville' => '',
+                            'id_sigb' => null,
+                            'is_contact_sms' => 0,
+                            'is_contact_mail' => 0,
+                            'date_maj' => '2013-10-23 11:47:38',
+                            'mobile' => '',
+                            'civilite' => 0,
+                            'ordreabon' => '',
+                            'id_panier_courant' => 0,
+                            'pseudo' => '',
+                            'settings' => '',
+                            'statut' => 0]);
+
+    $user->setMail('erik@truffaz.com')->save();
+    $this->assertEquals('erik@truffaz.com', Class_Users::find(189)->getMail());
   }
 }
 
@@ -302,31 +268,35 @@ class UsersSaveTest extends ModelTestCase {
 
 
 class UsersDeleteTest extends ModelTestCase {
+  protected $_storm_default_to_volatile = true;
 
-  protected function _expectDelete($id, $fixture) {
-    $this
-      ->_buildTableMock('Class_Users', array('delete'))
-      ->expects($this->once())
-      ->method('delete')
-      ->with("id_user='" . $id . "'");
 
-    Class_Users::getLoader()
-      ->newFromRow($fixture)
-      ->delete();
-  }
 
-  public function testDeleteMilesCallsDeleteWithIdOne() {
-    $this->_expectDelete(1, UserFixtures::miles());
+  /** @test */
+  public function deleteMilesShouldDeleteId1() {
+    $miles = $this->fixture('Class_Users',
+                            array_merge(['id' => 1,
+                                         'idabon' => 789],
+                                        UserFixtures::miles()));
+    Class_Users::find(1)->delete();
+    $this->assertNull(Class_Users::find(1));
   }
 
-  public function testDeleteTruffazCallsDeleteWithId34() {
-    $this->_expectDelete(34, UserFixtures::truffaz());
-  }
 
+  /** @test */
+  public function deleteTruffazShouldDeleteWithId34() {
+    $truffaz = $this->fixture('Class_Users',
+                              array_merge(['id' => 34,
+                                           'idabon' => 734],
+                                        UserFixtures::truffaz()));
+    Class_Users::find(34)->delete();
+    $this->assertNull(Class_Users::find(34));
+  }
 }
 
 
 
+
 class UsersTestAssociations extends ModelTestCase {
   protected $_storm_default_to_volatile = true;
 
diff --git a/tests/library/Class/WebService/Dilicom/HubTest.php b/tests/library/Class/WebService/Dilicom/HubTest.php
deleted file mode 100644
index d162e17295eff57ec25abd48810d5c67d422a004..0000000000000000000000000000000000000000
--- a/tests/library/Class/WebService/Dilicom/HubTest.php
+++ /dev/null
@@ -1,204 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012-2014, 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
- */
-require_once 'tests/fixtures/DilicomFixtures.php';
-
-abstract class Class_Webservice_Dilicom_HubTestCase extends ModelTestCase {
-  protected $_storm_default_to_volatile = true;
-
-  public function setUp() {
-    parent::setUp();
-
-    RessourcesNumeriquesFixtures::activateDilicom();
-
-    $this->_http = $this->mock();
-    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
-
-    $this->_response = json_encode(['requestId' => 'xxx',
-                                    'returnStatus' => 'OK',
-                                    'returnMessage' => []]);
-
-    $this->book = (new DilicomFixtures())->albumTotemThora();
-  }
-
-
-  public function tearDown() {
-    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
-    RessourcesNumeriquesFixtures::deactivateDilicom();
-    parent::tearDown();
-  }
-}
-
-
-
-class Class_Webservice_Dilicom_HubDeclareIpTest extends Class_Webservice_Dilicom_HubTestCase {
-  /** @test */
-  public function savingDilicomPnbIPAddressShouldCallWebserviceDeclareIP() {
-    $this->_http->whenCalled('open_url')
-                ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/declareIp?'
-                       .'glnContractor=123456789&ips[0]=10.12.13.24',
-                       ['auth' => [ 'user' => 'afi-bib',
-                                   'password' => 'secretPassword']])
-                ->answers($this->_response);
-
-    $this->fixture('Class_AdminVar',
-                   ['id' => 'DILICOM_PNB_IP_ADRESSES',
-                    'valeur' => '10.12.13.24']);
-
-    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
-  }
-
-
-  /** @test */
-  public function savingDilicomPnbIpAdressWithMultipleIpsShouldCallWebServiceDeclareIp() {
-    $this->_http->whenCalled('open_url')
-                ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/declareIp?'
-                       .'glnContractor=123456789&ips[0]=10.12.13.24&ips[1]=11.128.16.87',
-                       ['auth' => [ 'user' => 'afi-bib',
-                                   'password' => 'secretPassword']])
-                ->answers($this->_response);
-
-    $this->fixture('Class_AdminVar',
-                   ['id' => 'DILICOM_PNB_IP_ADRESSES',
-                    'valeur' => '10.12.13.24;11.128.16.87']);
-
-    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
-  }
-}
-
-
-
-
-class Class_Webservice_Dilicom_HubUpdateStatusSuccessfulTest extends Class_Webservice_Dilicom_HubTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->_wrapper = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Album_Item');
-
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getLoanStatus?glnContractor=123456789&orderLineId[0]=x321',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getLoanStatusResponse())
-      ->beStrict();
-
-    (new Class_WebService_BibNumerique_Dilicom_Hub())->updateStatus($this->book);
-    $this->item = $this->book->getItems()[0];
-  }
-
-
-  /** @test */
-  public function numberOfSimultaneousLoansRemaningShouldBeFiveOnSuccessfulUpddate() {
-    $this->assertEquals(5, $this->item->numberOfSimultaneousLoansRemaning());
-  }
-
-
-  /** @test */
-  public function loanQuantityRemainingShouldBe24OnSuccessfulUpdate() {
-    $this->assertEquals(24, $this->item->quantityOfLoansRemaining());
-  }
-
-
-  /** @test */
-  public function itemSaveShouldHaveBeenCalled() {
-    $this->assertTrue($this->_wrapper->methodHasBeenCalled('save'));
-  }
-}
-
-
-
-
-class Class_Webservice_Dilicom_HubUpdateStatusErrorTest extends Class_Webservice_Dilicom_HubTestCase {
-  /** @test */
-  public function numberOfSimultaneousLoansRemaningShouldNotHaveBeenUpdatedOnError() {
-    $this->_http
-      ->whenCalled('open_url')
-      ->with('https://pnb-test.centprod.com/v2/pnb-numerique/json/getLoanStatus?glnContractor=123456789&orderLineId[0]=x321',
-             ['auth' => [ 'user' => 'afi-bib',
-                         'password' => 'secretPassword']])
-      ->answers(DilicomFixtures::getLoanStatusErrorResponse())
-      ->beStrict();
-
-    (new Class_WebService_BibNumerique_Dilicom_Hub())->updateStatus($this->book);
-
-
-    $this->assertEquals(48,
-                        $this->book->getItems()[0]->numberOfSimultaneousLoansRemaning());
-  }
-}
-
-
-
-
-class Class_Webservice_Dilicom_UpdateLoansReturnDateTest extends Class_Webservice_Dilicom_HubTestCase {
-  protected
-    $_loans,
-    $_first_request,
-    $_second_request;
-
-  public function setUp() {
-    parent::setUp();
-
-    Storm_Cache::beVolatile();
-
-    $this->_loans = array_map(function($i)
-                       {
-                         return
-                           $this->fixture('Class_Loan_Pnb',
-                                          ['id' => $i,
-                                           'album' => $this->fixture('Class_Album',
-                                                                     ['id' => $i,
-                                                                      'titre' => 'Dr House',
-                                                                      'id_origine' => 'Dilicom-' . $i])
-                                          ]);
-                       },
-                       range(1, 35));
-
-    $this->_first_request = 'https://pnb-test.centprod.com/v2/pnb-numerique/json/getEndedLoans?glnContractor=123456789&loanId[0]=1&loanId[1]=2&loanId[2]=3&loanId[3]=4&loanId[4]=5&loanId[5]=6&loanId[6]=7&loanId[7]=8&loanId[8]=9&loanId[9]=10&loanId[10]=11&loanId[11]=12&loanId[12]=13&loanId[13]=14&loanId[14]=15&loanId[15]=16&loanId[16]=17&loanId[17]=18&loanId[18]=19&loanId[19]=20';
-
-    $this->_second_request = 'https://pnb-test.centprod.com/v2/pnb-numerique/json/getEndedLoans?glnContractor=123456789&loanId[0]=21&loanId[1]=22&loanId[2]=23&loanId[3]=24&loanId[4]=25&loanId[5]=26&loanId[6]=27&loanId[7]=28&loanId[8]=29&loanId[9]=30&loanId[10]=31&loanId[11]=32&loanId[12]=33&loanId[13]=34&loanId[14]=35';
-
-    $this->_http
-      ->whenCalled('open_url')->with($this->_first_request,
-                                     ['auth' => [ 'user' => 'afi-bib',
-                                         'password' => 'secretPassword']])->answers('')
-      ->whenCalled('open_url')->with($this->_second_request,
-                                     ['auth' => [ 'user' => 'afi-bib',
-                                                 'password' => 'secretPassword']])->answers('')->beStrict();
-
-    (new Class_WebService_BibNumerique_Dilicom_Hub())->updateLoansReturnDate($this->_loans);
-  }
-
-
-  /** @test */
-  public function httpRequestCountShouldBeTwo() {
-    $this->assertEquals(2, $this->_http->methodCallCount('open_url'));
-  }
-
-
-  /** @test */
-  public function secondCallOnUpdateLoansReturnDateShouldBeCached() {
-    (new Class_WebService_BibNumerique_Dilicom_Hub())->updateLoansReturnDate($this->_loans);
-    $this->assertEquals(2, $this->_http->methodCallCount('open_url'));
-  }
-}
-
-?>
\ No newline at end of file
diff --git a/tests/library/Class/WebService/Dilicom/ONIXParserTest.php b/tests/library/Class/WebService/Dilicom/ONIXParserTest.php
deleted file mode 100644
index 025ad39b5f1b42ec1c96c2737cd1cf394eb1031f..0000000000000000000000000000000000000000
--- a/tests/library/Class/WebService/Dilicom/ONIXParserTest.php
+++ /dev/null
@@ -1,147 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012, 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 DilicomONIXParserTest extends Storm_Test_ModelTestCase {
-  public function setUp() {
-    parent::setUp();
-    $xmlpath = realpath(dirname(__FILE__)) . '/fixtures/la_mer.xml';
-    $xml = file_get_contents($xmlpath);
-
-    Storm_Model_Loader::defaultToVolatile();
-    $this->_book = Class_WebService_BibNumerique_Dilicom_ONIXFile::bookFromXML($xml);
-  }
-
-  public function tearDown() {
-    Storm_Model_Loader::defaultToDb();
-    parent::tearDown();
-  }
-
-
-  /** @test */
-  public function bookShouldNotBeNull() {
-    $this->assertNotNull($this->_book);
-  }
-
-
-  /** @test */
-  public function titleShouldBeCertainesNAvaientJamaisVuLaMer() {
-    $this->assertEquals('Certaines n\'avaient jamais vu la mer', $this->_book->getTitle());
-  }
-
-
-  /** @test */
-  public function isbnShouldBe9782752908643() {
-    $this->assertEquals('9782752908643', $this->_book->getIsbn());
-  }
-
-
-  /** @test */
-  public function mainAuthorShouldBeJulieOtsuka() {
-    $this->assertEquals('Julie Otsuka', $this->_book->getAuthors()[0]);
-  }
-
-
-  /** @test */
-  public function publisherShouldBePhebus() {
-    $this->assertEquals('Phébus', $this->_book->getEditeur());
-  }
-
-
-  /** @test */
-  public function publishingDateShouldBe2012() {
-    $this->assertEquals(2012, $this->_book->getYear());
-  }
-
-
-  /** @test */
-  public function descriptionShouldContainsPrixFemina() {
-    $this->assertContains('Prix Femina étranger 2012', $this->_book->getDescription());
-  }
-
-
-  /** @test */
-  public function languageShouldBeFRE() {
-    $this->assertEquals('fre', $this->_book->getIdLangue());
-  }
-
-
-  /** @test */
-  public function importAlbumTypeDocShouldBeDILICOM() {
-    $album = $this->_book->import();
-    $this->assertEquals(Class_TypeDoc::DILICOM, $album->getTypeDocId());
-    return $album;
-  }
-
-
-  /** @test */
-  public function albumRightShouldBeOther() {
-    $album = $this->_book->import();
-    $this->assertEquals('Tous droits réservés', $album->getDroits());
-  }
-
-
-  /**
-   * @depends importAlbumTypeDocShouldBeDILICOM
-   * @test
-   */
-  public function albumFieldTenDollarAShouldBeISBN9782752908643($album) {
-    $this->assertEquals('9782752908643', $album->getMarc()->getSubfield('10', 'a')[0]);
-  }
-
-
-  /**
-   * @depends importAlbumTypeDocShouldBeDILICOM
-   * @test
-   */
-  public function albumMainAuthorShouldBeJulieOtsuka($album) {
-    $this->assertEquals('Julie Otsuka', $album->getMainAuthorName());
-  }
-
-
-  /**
-   * @depends importAlbumTypeDocShouldBeDILICOM
-   * @test
-   */
-  public function albumPublisherShouldContainsPhebus($album) {
-    $this->assertContains('Phébus', $album->getEditors());
-  }
-
-
-  /**
-   * @depends importAlbumTypeDocShouldBeDILICOM
-   * @test
-   */
-  public function albumCollectionShouldContainsLitteratureEtrangere($album) {
-    $this->assertContains('Littérature étrangère', $album->getCollections());
-  }
-
-
-  /**
-   * @depends importAlbumTypeDocShouldBeDILICOM
-   * @test
-   */
-  public function albumCategoryShouldBeLivreNumeriquePNB($album) {
-    $this->assertEquals('Livre numérique (PNB)', $album->getCategorie()->getLibelle());
-  }
-
-}
-?>
\ No newline at end of file
diff --git a/tests/library/Class/WebService/Dilicom/PNBOffersParserTest.php b/tests/library/Class/WebService/Dilicom/PNBOffersParserTest.php
deleted file mode 100644
index c013959149250129a38ab0e65a2f63823f4365c1..0000000000000000000000000000000000000000
--- a/tests/library/Class/WebService/Dilicom/PNBOffersParserTest.php
+++ /dev/null
@@ -1,466 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012, 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
- */
-
-
-abstract class DilicomPNBOfferParserTestCase extends ModelTestCase {
-  protected
-    $_storm_default_to_volatile = true,
-    $_pnb_filename = 'full_pnb_666_20150220T150017Z.xml',
-    $_xml;
-
-  public function setUp() {
-    parent::setUp();
-    $xmlpath = realpath(dirname(__FILE__)) . '/fixtures/' . $this->_pnb_filename;
-    $this->_xml = file_get_contents($xmlpath);
-
-    $this->_http_client = Storm_Test_ObjectWrapper::mock()
-                                                    ->whenCalled('open_url')
-                                                    ->answers('');
-    Class_WebService_Abstract::setDefaultHttpClient($this->_http_client);
-
-    Class_Album_UsageConstraint::setTimeSource(new TimeSourceForTest('2014-05-02 14:14:14'));
-  }
-
-
-  public function tearDown() {
-    Class_WebService_Abstract::setDefaultHttpClient(null);
-    parent::tearDown();
-  }
-}
-
-
-
-
-class DilicomPNBOffersParserTest extends DilicomPNBOfferParserTestCase {
-  public function setUp() {
-    parent::setUp();
-    Class_Album_UsageConstraints::setTimeSource(new TimeSourceForTest('2015-11-24 09:00:00'));
-    $this->_books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
-  }
-
-
-  /** @test */
-  public function numberOfBooksShouldBeThree() {
-    $this->assertEquals(3, count($this->_books));
-  }
-
-
-  /** @test */
-  public function firstAlbumShouldBePlusJamaisSansElle() {
-    $album = $this->_books[0]->import();
-    $this->assertEquals('Plus jamais sans elle', $album->getTitre());
-
-    return $album;
-  }
-
-
-  /** @test */
-  public function firstBookExternalURIShouldBeEdenLivres() {
-    $this->assertEquals('http://www.edenlivres.fr/p/23416', $this->_books[0]->getExternalUri());
-  }
-
-
-  /** @test */
-  public function firstBookPostersShouldContainsFrontCoverDotJpg() {
-    $this->assertContains('https://assets.edenlivres.fr/assets/publications/23416/medias/front_cover.jpg',
-                          $this->_books[0]->getPosters());
-  }
-
-
-  /** @test */
-  public function firstBookPostersShouldContainsFrontCoverSmallDotJpg() {
-    $this->assertContains('http://assets.edenlivres.fr/assets/publications/23416/cover/small.jpg',
-                          $this->_books[0]->getPosters());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldBePlusJamaisSansElle
-   * @test
-   */
-  public function firstAlbumThumbnailShouldBeSmallDotJpg($album) {
-    $this->assertEquals('http://assets.edenlivres.fr/assets/publications/23416/cover/small.jpg',
-                        $album->getPoster());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldBePlusJamaisSansElle
-   * @test
-   */
-  public function firstAlbumShouldHaveTwoItems($album) {
-    $this->assertCount(2, $album->getItems());
-    return $album->getItems()[0];
-  }
-
-
-  /**
-   * @depends firstAlbumShouldHaveTwoItems
-   * @test
-   */
-  public function firstAlbumFirstItemShouldHaveSixUsageConstraints($item) {
-    $this->assertCount(6, $item->getUsageConstraints());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldHaveTwoItems
-   * @test
-   */
-  public function firstAlbumFirstItemItemLoanDurationShouldBe59($item) {
-    $this->assertEquals(59, $item->getUsageConstraints()->getLoanDuration());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldHaveTwoItems
-   * @test
-   */
-  public function firstAlbumFirstItemLoanOrderLineIdShouldBe54e748d8975a2fa6aa4d3e25($item) {
-    $this->assertEquals('54e7473f975a2fa6aa4d3e17',
-                        $item->getUsageConstraints()->getLoanOrderLineId());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldHaveTwoItems
-   * @test
-   */
-  public function firstAlbumFirstItemLoanOrderDateShouldBe2015_02_20($item) {
-    $this->assertEquals('2015-02-20T15:40:01.866+01:00',
-                        $item->getUsageConstraints()->getLoanOrderDate());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldHaveTwoItems
-   * @test
-   */
-  public function firstAlbumFirstItemLoanQuantityShouldBe40($item) {
-    $this->assertEquals(40, $item->getUsageConstraints()->getLoanQuantity());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldHaveTwoItems
-   * @test
-   */
-  public function firstAlbumFirstItemAvailabilityDurationShouldBe1825($item) {
-    $this->assertEquals(1825, $item->getUsageConstraints()->getAvailabilityDuration());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldHaveTwoItems
-   * @test
-   */
-  public function firstAlbumFirstItemAvailabilityEndDateBeShouldBe2020_19_02($item) {
-    $this->assertEquals('2020-02-19T15:40:01+0100', $item->getUsageConstraints()->getAvailabilityEndDate());
-  }
-
-
-   /**
-    * @depends firstAlbumShouldHaveTwoItems
-    * @test
-    */
-  public function firstAlbumFirstItemLoanMaxNumberOfUsersShouldBe15($item) {
-    $this->assertEquals(15,
-                        $item->getUsageConstraints()->getLoanMaxNumberOfUsers());
-  }
-
-
-   /**
-    * @depends firstAlbumShouldHaveTwoItems
-    * @test
-    */
-  public function albumLicenseShouldBeAvailableFor1548Days($item) {
-    $this->assertEquals(1548,
-                        $item->getUsageConstraints()->getAvailabilityRemainingDaysBeforeEndDate());
-  }
-
-
-  /** @test */
-  public function secondAlbumShouldBeJournalDunDegonfle() {
-    $album = $this->_books[1]->import();
-    $this->assertEquals('Journal d\'un dégonflé, t.1', $album->getTitre());
-    return $album;
-  }
-
-
-  /**
-   * @depends secondAlbumShouldBeJournalDunDegonfle
-   * @test
-   */
-  public function secondAlbumSubtitleShouldBeCarnetDeBordDeGregHeffley($album) {
-    $this->assertEquals('Carnet de bord de Greg Heffley', $album->getSousTitre());
-  }
-
-
-  /**
-   * @test
-   */
-  public function secondBookIdShouldBeDilicom9791023501735() {
-    $this->assertEquals('Dilicom-9791023501735', $this->_books[1]->getId());
-  }
-
-
-  /**
-   * @depends secondAlbumShouldBeJournalDunDegonfle
-   * @test
-   */
-  public function secondAlbumUrlOriginShouldBeDilicom($album) {
-    $this->assertEquals('http://www.dilicom.net', $album->getUrlOrigine());
-  }
-
-
-  /** @test */
-  public function thirdAlbumShouldBeHomerEtLeChienFormidable() {
-    $album = $this->_books[2]->import();
-    $this->assertEquals('Homer et le chien formidable', $album->getTitre());
-    return $album;
-  }
-
-
-
-  /**
-   * @depends thirdAlbumShouldBeHomerEtLeChienFormidable
-   * @test
-   */
-  public function thirdAlbumISBNShouldBe9791023504149($album) {
-    $this->assertEquals('9791023504149', $album->getIsbn());
-  }
-
-
-
-  /**
-   * @depends firstAlbumShouldBePlusJamaisSansElle
-   * @test
-   */
-  public function removeUsageConstraintsAndAskForLoanQuantityShouldAnswers0($item) {
-    $item->setUsageConstraints(new Class_Album_UsageConstraints());
-    $this->assertEquals(0, $item->getUsageConstraints()->getLoanQuantity());
-  }
-
-
-  /**
-   * @depends firstAlbumShouldBePlusJamaisSansElle
-   * @test
-   */
-  public function removeUsageConstraintsAndAskForValidityOfferShouldReturnFalse($item) {
-    $item->setUsageConstraints(new Class_Album_UsageConstraints());
-    $this->assertFalse($item->getUsageConstraints()->isAValidOffer());
-  }
-
-}
-
-
-
-
-class DilicomPNBOffersParserWithLoanDurationVariableTest extends DilicomPNBOfferParserTestCase {
-  protected $_item;
-
-  public function setUp() {
-    parent::setUp();
-    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', '21');
-
-    $this->_books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
-    $this->_item = $this->_books[0]->import()->getItems()[0];
-  }
-
-
-  /**
-   * @test
-   */
-  public function firstAlbumLoanDurationShouldBe21() {
-    $this->assertEquals(21,
-                        $this->_item->getUsageConstraints()->getLoanDuration());
-  }
-
-
-  /**
-   * @test
-   */
-  public function firstAlbumAvailabilityEndDateBeShouldBe2020_02_19() {
-    $album = $this->_books[0]->import();
-    $this->assertEquals('2020-02-19T15:40:01+0100',
-                        $this->_item->getUsageConstraints()->getAvailabilityEndDate());
-  }
-}
-
-
-
-
-class DilicomPNBOffersParserUpdateWithNewOrderTest extends DilicomPNBOfferParserTestCase {
-  public function setUp() {
-    parent::setUp();
-    Class_Album_UsageConstraints::setTimeSource(new TimeSourceForTest('2015-03-31 09:00:00'));
-    $books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
-    foreach($books as $book)
-      $book->import();
-
-    $xmlpath = realpath(dirname(__FILE__)) . '/fixtures/partial_pnb_666_20150220T150017Z.xml';
-
-    $this->_books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML(file_get_contents($xmlpath));
-
-    foreach($this->_books as $book)
-      $book->import();
-
-    Class_Album::clearCache();
-
-    $this->_items = Class_Album::findFirstBy(['id_origine' => 'Dilicom-9791023504148'])->getItems();
-  }
-
-
-  /** @test */
-  public function albumHomerShouldHaveTwoItems() {
-    $this->assertCount(2,
-                       $this->_items);
-  }
-
-
-  /** @test */
-  public function firstItemOrderLineIdShouldBe54e74() {
-    $this->assertEquals('54e748d8975a2fa6aa4d3e25', $this->_items[0]->getOrderLineId());
-  }
-
-
-  /** @test */
-  public function secondItemOrderLineIdShouldBe1234() {
-    $this->assertEquals('123456789abcde',
-                        $this->_items[1]->getOrderLineId());
-  }
-
-
-  /** @test */
-  public function secondItemAvailabilityDurationShouldBe1000() {
-    $this->assertEquals(1000,
-                        $this->_items[1]->getUsageConstraints()->getAvailabilityDuration());
-  }
-}
-
-
-
-class DilicomDecitresPNBOffersParserTest extends DilicomPNBOfferParserTestCase {
-  protected $_pnb_filename = 'full_pnb_decitres.xml';
-
-  public function setUp() {
-    parent::setUp();
-    $this->_books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
-  }
-
-
-  /** @test */
-  public function numberOfBooksShouldBeFour() {
-    $this->assertEquals(4, count($this->_books));
-  }
-
-
-  /** @test */
-  public function firstBookShouldBeEspritRouge() {
-    $album = $this->_books[0]->import();
-    $this->assertEquals('L’Esprit rouge', $album->getTitre());
-
-    return $album;
-  }
-
-
-  /**
-   * @depends firstBookShouldBeEspritRouge
-   * @test
-   */
-  public function firstBookThumbnailShouldBeSmallDotJpg($album) {
-    $this->assertEquals('https://assets.edenlivres.fr/assets/publications/171925/cover/small.jpg',
-                        $album->getPoster());
-  }
-
-
-  /** @test */
-  public function secondBookShouldBeCompagnons() {
-    $album = $this->_books[1]->import();
-    $this->assertEquals('Compagnons', $album->getTitre());
-
-    return $album;
-  }
-
-
-  /**
-   * @depends secondBookShouldBeCompagnons
-   * @test
-   */
-  public function secondBookDescriptionShouldContainsJeuneApprentie($album) {
-    $this->assertContains('Émilie, jeune apprentie au caractère entier',
-                          $album->getDescription());
-  }
-}
-
-
-
-
-class DilicomRiviereEsperancePNBOffersParserTest extends DilicomPNBOfferParserTestCase {
-  protected
-    $_pnb_filename = 'riviere_esperance.xml',
-    $_item;
-
-  public function setUp() {
-    parent::setUp();
-    $books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
-    $books[0]->import();
-    $this->_item = Class_Album_Item::find(1);
-  }
-
-
-  /** @test */
-  public function itemLoanMaxNumberOfUsersShouldBe30() {
-    $this->assertEquals(30,
-                        $this->_item->getUsageConstraints()->getLoanMaxNumberOfUsers());
-  }
-
-
-  /** @test */
-  public function itemLoanQuantityShouldBe50() {
-    $this->assertEquals(50,
-                        $this->_item->getUsageConstraints()->getLoanQuantity());
-  }
-}
-
-
-
-
-class DilicomProduitsTerroirPNBOffersParserTest extends DilicomPNBOfferParserTestCase {
-  protected
-    $_pnb_filename = 'produits_terroir.xml',
-    $_item;
-
-  public function setUp() {
-    parent::setUp();
-    $books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
-    $books[0]->import();
-    $this->_item = Class_Album_Item::find(1);
-  }
-
-
-  /** @test */
-  public function itemLoanDurationShouldBe59() {
-    $this->assertEquals(59,
-                        $this->_item->getUsageConstraints()->getLoanDuration());
-  }
-}
-?>
\ No newline at end of file
diff --git a/tests/library/Class/WebService/SIGB/OpsysServiceTest.php b/tests/library/Class/WebService/SIGB/OpsysServiceTest.php
index 81a8167d631667178b5ddd1ad66b2e6b4877fa77..67a8d2e00b9ff9f83f24123f3214294dcf25a7e8 100644
--- a/tests/library/Class/WebService/SIGB/OpsysServiceTest.php
+++ b/tests/library/Class/WebService/SIGB/OpsysServiceTest.php
@@ -216,80 +216,67 @@ class Class_System_OpsysServiceFactoryTestUrls extends ModelTestCase {
 
 
 
+
 class Class_WebService_SIGB_OpsysServiceTestProxy extends ModelTestCase {
-  private $factory;
-  private $mock_opsys_service;
 
-  public function setUp(){
-    $this->factory = $this->createMock('OpsysFactory', array('createOpsysService'));
+  protected $_storm_default_to_volatile = true,
+    $factory,
+    $_opsys_service;
 
-    $this->mock_opsys_service = $this->createMock(
-                                         'Class_WebService_SIGB_Opsys',
-                                         array('newOpsysServiceFactory'));
 
-    $this->mock_opsys_service->expects($this->once())
-      ->method('newOpsysServiceFactory')
-      ->will($this->returnValue($this->factory));
+  public function setUp() {
+    parent::setUp();
+    $this->factory = $this->mock();
+    $this->factory->beStrict();
+    $this->_opsys_service = (new Class_WebService_SIGB_Opsys)->setServiceFactory($this->factory);
   }
 
 
-  public function testCreateServiceWithProxy(){
+  /** @test */
+  public function createServiceWithProxyShouldReturnService() {
     Class_WebService_SIGB_Opsys::setProxy('192.168.2.2', '3128', 'login', 'password');
 
-    $this->factory->expects($this->once())
-      ->method('createOpsysService')
-      ->with(
-             $this->equalTo('opsys.wsdl'),
-             $this->equalTo(true),
-             $this->equalTo(false),
-             $this->equalTo( array(
-                                   'proxy_host' => '192.168.2.2',
-                                   'proxy_port' => '3128',
-                                   'proxy_login' => 'login',
-                                   'proxy_password' =>  'password')))
-      ->will($this->returnValue('anOpsysService'));
-
-    $service = $this->mock_opsys_service->createService('opsys.wsdl');
+    $this->factory
+      ->whenCalled('createOpsysService')
+      ->with('opsys.wsdl',
+             true,
+             false,
+             ['proxy_host' => '192.168.2.2',
+              'proxy_port' => '3128',
+              'proxy_login' => 'login',
+              'proxy_password' =>  'password'])
+      ->answers('anOpsysService');
+
+    $service = $this->_opsys_service->createService('opsys.wsdl');
     $this->assertEquals('anOpsysService', $service);
   }
 
 
-  public function testCreateServiceWithProxyInZendRegistry(){
-    $proxyConfig = array(
-                         'proxy_host' => '10.0.0.5',
-                         'proxy_port' => '8080',
-                         'proxy_user' => 'tintin',
-                         'proxy_pass' => 'milou');
-    Zend_Registry::set('http_proxy',$proxyConfig);
-
-    $this->factory->expects($this->once())
-      ->method('createOpsysService')
-      ->with(
-             $this->equalTo('afi.wsdl'),
-             $this->equalTo(true),
-             $this->equalTo(false),
-             $this->equalTo( array(
-                                   'proxy_host' => '10.0.0.5',
-                                   'proxy_port' => '8080',
-                                   'proxy_login' => 'tintin',
-                                   'proxy_password' =>  'milou')))
-      ->will($this->returnValue('anAFIService'));
-
-    $service = $this->mock_opsys_service->createService('afi.wsdl');
+  /** @test */
+  public function createServiceWithProxyInZendRegistry() {
+    Zend_Registry::set('http_proxy',
+                       ['proxy_host' => '10.0.0.5',
+                        'proxy_port' => '8080',
+                        'proxy_user' => 'tintin',
+                        'proxy_pass' => 'milou']);
+
+    $this->factory
+      ->whenCalled('createOpsysService')
+      ->with('afi.wsdl',
+             true,
+             false,
+             ['proxy_host' => '10.0.0.5',
+              'proxy_port' => '8080',
+              'proxy_login' => 'tintin',
+              'proxy_password' =>  'milou'])
+      ->answers('anAFIService');
+
+    $service = $this->_opsys_service->createService('afi.wsdl');
     $this->assertEquals('anAFIService', $service);
   }
+}
 
 
-  public function testCreateServiceWithoutProxy(){
-    $this->factory->expects($this->once())
-      ->method('createOpsysService')
-      ->with($this->equalTo('opsys.wsdl'))
-      ->will($this->returnValue('anOpsysService'));
-
-    $service = $this->mock_opsys_service->createService('opsys.wsdl');
-    $this->assertEquals('anOpsysService', $service);
-  }
-}
 
 
 class OpsysServiceNoticeTestDispoExemplaire extends ModelTestCase {
@@ -1747,6 +1734,4 @@ class OpsysServiceDisconnectTest extends OpsysServiceWithSessionTestCase {
   public function serveurSessionNomServeurShouldBeInternet() {
     $this->assertEquals('INTERNET', $this->_fermer_session->Param->ListeServeurs[0]->NomServeur);
   }
-}
-
-?>
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/library/ZendAfi/View/Helper/Abonne/ResumeTest.php b/tests/library/ZendAfi/View/Helper/Abonne/ResumeTest.php
index e37903cd1bc566c9ba8708545d522576877313e0..11db1116496bce44967663f779bc5f72e6cb9591 100644
--- a/tests/library/ZendAfi/View/Helper/Abonne/ResumeTest.php
+++ b/tests/library/ZendAfi/View/Helper/Abonne/ResumeTest.php
@@ -65,7 +65,7 @@ class View_Helper_Abonne_ResumeAsAbonneSIGBTest extends ViewHelperTestCase {
 
     $this->_http = $this->mock()
                         ->whenCalled('open_url')
-                        ->with('/v2/pnb-numerique/json/getEndedLoans?glnContractor=777&loanId[0]=5&loanId[1]=6',
+                        ->with('/v3/pnb-numerique/json/getEndedLoans?login=test-gln&password=test-gln-pass&glnContractor=777&loanId%5B0%5D=5&loanId%5B1%5D=6',
                                ['auth' => [ 'user' => 'test-gln',
                                    'password' => 'test-gln-pass']])
                         ->answers(DilicomFixtures::getEndedLoansResponse())
diff --git a/tests/library/ZendAfi/View/Helper/Accueil/PretsTest.php b/tests/library/ZendAfi/View/Helper/Accueil/PretsTest.php
index c2cf816d1820b7048c9a0f644783c42820156c26..0578c0d70c739e45d369a054840a88d30db0761e 100644
--- a/tests/library/ZendAfi/View/Helper/Accueil/PretsTest.php
+++ b/tests/library/ZendAfi/View/Helper/Accueil/PretsTest.php
@@ -67,7 +67,7 @@ class PretsTestWithConnectedUser extends ViewHelperTestCase {
 
     $http_client = $this->mock()
                         ->whenCalled('open_url')
-                        ->with('/v2/pnb-numerique/json/getEndedLoans?glnContractor=&loanId[0]=12',
+                        ->with('/v3/pnb-numerique/json/getEndedLoans?loanId%5B0%5D=12',
                                ['auth' => [ 'user' => null,
                                            'password' => null]])
                         ->answers('')
diff --git a/tests/library/ZendAfi/View/Helper/Notice/DilicomTest.php b/tests/library/ZendAfi/View/Helper/Notice/DilicomTest.php
deleted file mode 100644
index 994226a41546ac465542f84dc39ebef691116d4d..0000000000000000000000000000000000000000
--- a/tests/library/ZendAfi/View/Helper/Notice/DilicomTest.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?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
- */
-
-
-
-abstract class ZendAfi_View_Helper_Notice_DilicomTestCase extends ViewHelperTestCase {
-  protected $_storm_default_to_volatile = true;
-
-
-  public function setUp() {
-    parent::setUp();
-    $this->_helper = new ZendAfi_View_Helper_Notice_Dilicom();
-    $this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
-    $this->_prepareFixture();
-    $this->_html = $this->_helper->Notice_Dilicom($this->_record);
-  }
-
-
-  protected function _prepareFixture() {
-    $this->_record = $this->fixture('Class_Notice',
-                                    ['id' => 1,
-                                     'type_doc' => Class_TypeDoc::DILICOM,
-                                     'clef_alpha' => 'POMMEETANANAS',
-                                     'alpha_titre' => 'POM POMS ANANA ANANAS',
-                                     'alpha_auteur' => 'AUTEUR AUTEURS',
-                                     'unimarc' => '']);
-  }
-}
-
-
-
-
-class ZendAfi_View_Helper_Notice_DilicomNotLoggedTest extends ZendAfi_View_Helper_Notice_DilicomTestCase {
-  /** @test */
-  public function shouldRenderLoginForm() {
-    $this->assertXPath($this->_html, '//form', $this->_html);
-  }
-}
-
-
-
-class ZendAfi_View_Helper_Notice_DilicomLoggedTest extends ZendAfi_View_Helper_Notice_DilicomTestCase {
-
-  protected function _prepareFixture() {
-    parent::_prepareFixture();
-    $user = $this->fixture('Class_Users',
-                           ['id' => 6,
-                            'login' => 'admin',
-                            'password' => 'admin',
-                            'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL]);
-
-    ZendAfi_Auth::getInstance()->logUser($user);
-
-    $this->fixture('Class_Album',
-                   ['id' => 2,
-                    'titre' => 'Musso Dilicom',
-                    'type_doc_id' => Class_TypeDoc::DILICOM,
-                    'id_origine' => 'dilicom-123456',
-                    'url_origine' => 'https://url_dilicom.org/ressource/id/1',
-                    'notice_id' => 1]);
-
-    $this->fixture('Class_Exemplaire',
-                   ['id' => 564,
-                    'id_notice' => 1,
-                    'id_origine' => 'dilicom-123456']);
-  }
-
-
-  /** @test */
-  public function shouldRenderDilicomAlbumDatas() {
-    $this->assertXPathContentContains($this->_html, '//table//td', 'dilicom-123456');
-  }
-}
diff --git a/tests/library/ZendAfi/View/Helper/RenderAlbumTest.php b/tests/library/ZendAfi/View/Helper/RenderAlbumTest.php
index 7769c4262d20dc9e896f4025e9a7088b30a4da26..9d4e82ce4230954cbc5400d74fdc7e8e4f81aacc 100644
--- a/tests/library/ZendAfi/View/Helper/RenderAlbumTest.php
+++ b/tests/library/ZendAfi/View/Helper/RenderAlbumTest.php
@@ -19,7 +19,6 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-require_once 'tests/fixtures/DilicomFixtures.php';
 
 abstract class ZendAfi_View_Helper_RenderAlbumTestCase extends ViewHelperTestCase {
   protected $_storm_default_to_volatile = true;
@@ -252,182 +251,4 @@ class ZendAfi_View_Helper_RenderAlbumCyberlibrisTest extends ZendAfi_View_Helper
                        '//a[@href="http://www.bibliovox.com/my_lib?docid=88817216"][@target="_blank"]',
                        $html);
   }
-}
-
-
-
-
-abstract class ZendAfi_View_Helper_RenderAlbumDilicomPNBTestCase extends ZendAfi_View_Helper_RenderAlbumTestCase {
-  public function setUp() {
-    parent::setUp();
-
-    $this->book = (new DilicomFixtures())->albumTotemThora();
-    RessourcesNumeriquesFixtures::activateDilicom();
-
-    $this->_http = Storm_Test_ObjectWrapper::mock();
-    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
-
-    $this->_time_source = new TimeSourceForTest('2014-05-02 14:14:14');
-    Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource($this->_time_source);
-    Class_Album_UsageConstraint::setTimeSource($this->_time_source);
-
-
-    $this->_http
-      ->whenCalled('setAuth')
-      ->answers(null)
-
-      ->whenCalled('open_url')
-      ->answers(DilicomFixtures::getLoanStatusResponse());
-
-  }
-
-
-  public function tearDown() {
-    ZendAfi_Auth::getInstance()->clearIdentity();
-    parent::tearDown();
-  }
-}
-
-
-
-
-class ZendAfi_View_Helper_RenderAlbumDilicomPNBNotLoggedTest extends ZendAfi_View_Helper_RenderAlbumDilicomPNBTestCase {
-  public function setUp() {
-    parent::setUp();
-    ZendAfi_Auth::getInstance()->clearIdentity();
-    $this->_html = $this->_helper->renderAlbum($this->book);
-  }
-
-
-  /** @test */
-  public function htmlShouldNotContainsLinkToConsultBook() {
-    $this->assertXPathContentContains($this->_html,
-                                      '//a[contains(@href, "/bib-numerique/consult-book-ajax/id/3")]',
-                                      'Consulter le livre en ligne');
-  }
-
-
-}
-
-
-
-
-class ZendAfi_View_Helper_RenderAlbumDilicomPNBLoggedButNotAuthorizeTest extends ZendAfi_View_Helper_RenderAlbumDilicomPNBTestCase {
-  public function setUp() {
-    parent::setUp();
-    $this->logged_user = $this->fixture('Class_Users',
-                                        ['id' => 6,
-                                         'nom'=>'Pito',
-                                         'login'=>'Chat',
-                                         'password'=>'123456',
-                                         'id_site' => 1,
-                                         'idabon' => '12345']);
-
-    $this->logged_user->setUserGroups([]);
-    ZendAfi_Auth::getInstance()->logUser($this->logged_user);
-    $this->_html = $this->_helper->renderAlbum($this->book);
-  }
-
-
-  /** @test */
-  public function htmlShouldNotContainsLinkToConsultBook() {
-    $this->assertXPathContentContains($this->_html,
-                                      '//p',utf8_encode("Vous n'avez pas le droit d'accéder à la consultation en ligne."));
-  }
-
-
-}
-
-
-
-
-class ZendAfi_View_Helper_RenderAlbumDilicomPNBTest extends ZendAfi_View_Helper_RenderAlbumDilicomPNBTestCase {
-
-  public function setUp() {
-    parent::setUp();
-
-    $this->fixture('Class_Bib', ['id' => 1,
-                                 'libelle' => 'Annecy',
-                                 'gln' => '333']);
-
-    $this->logged_user = $this->fixture('Class_Users',
-                                        ['id' => 6,
-                                         'nom'=>'Pito',
-                                         'login'=>'Chat',
-                                         'password'=>'123456',
-                                         'id_site' => 1,
-                                         'idabon' => '12345',
-                                         'user_groups' => [$this->fixture('Class_UserGroup',
-                                                                          ['id' => '20',
-                                                                           'libelle' => 'Multimedia',
-                                                                           'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]])]]);
-
-    $this->logged_user->beAbonneSIGB()->assertSave();
-    ZendAfi_Auth::getInstance()->logUser($this->logged_user);
-
-    $this->fixture('Class_Loan_Pnb', ['id' => 1,
-                                      'record_origin_id' => 'Dilicom-88817216',
-                                      'user_id' => '6']);
-
-    $this->_html = $this->_helper->renderAlbum($this->book);
-  }
-
-
-  public function tearDown() {
-    unset($_SERVER['HTTPS']);
-    parent::tearDown();
-  }
-
-
-  /** @test */
-  public function htmlShouldContainsIFrameOnEdenBook() {
-    $this->assertXPath($this->_html,
-                       '//iframe[@src="http://www.edenlivres.fr/p/23416"][@width="100%"][@height="600px"]',
-                       $this->_html);
-  }
-
-
-  /** @test */
-  public function htmlShouldContainsLinkToConsultBook() {
-    $this->assertXPathContentContains($this->_html,
-                                      '//a[contains(@href, "/bib-numerique/consult-book-ajax/id/3")]',
-                                      'Consulter le livre en ligne');
-  }
-
-
-  /** @test */
-  public function htmlShouldContainsLinkToLoanBook() {
-    $this->assertXPathContentContains($this->_html,
-                                      '//a[contains(@href, "/bib-numerique/loan-book-ajax/id/3")]',
-                                      'Emprunter le livre au format EPUB');
-  }
-
-
-  /** @test */
-  public function bibWithoutGlnShouldReturnErrorMessage() {
-    Class_Bib::find(1)->setGln('')->assertSave();
-    $this->_html = $this->_helper->renderAlbum($this->book);
-    $this->assertXPathContentContains($this->_html,
-                                      '//p', utf8_encode('Annecy n\'a pas accès à la consultation en ligne'));
-  }
-
-
-  /** @test */
-  public function userWithoutBibShouldReturnErrorMessage() {
-    $this->logged_user->setBib(null)->save();
-    $this->_html = $this->_helper->renderAlbum($this->book);
-    $this->assertXPathContentContains($this->_html,
-                                      '//p', utf8_encode('Vous devez être inscrit dans une bibliothèque pour accéder à la consultation en ligne'));
-
-  }
-
-  /** @test */
-  public function withHttpsPreviewSrcShouldBeHttps() {
-    $_SERVER['HTTPS'] = 'on';
-
-    $this->_html = $this->_helper->renderAlbum($this->book);
-    $this->assertXPath($this->_html,
-                       '//iframe[@src="https://www.edenlivres.fr/p/23416"][@width="100%"][@height="600px"]',
-                       $this->_html);
-  }
-}
+}
\ No newline at end of file
diff --git a/tests/scenarios/PnbDilicom/PnbDilicomTest.php b/tests/scenarios/PnbDilicom/PnbDilicomTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..62068969f74c12c3df37ca37b2168575dfdc2531
--- /dev/null
+++ b/tests/scenarios/PnbDilicom/PnbDilicomTest.php
@@ -0,0 +1,3345 @@
+<?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
+ */
+
+
+require_once 'tests/fixtures/DilicomFixtures.php';
+
+abstract class PnbDilicomHubTestCase extends ModelTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    RessourcesNumeriquesFixtures::activateDilicom();
+
+    $this->_http = $this->mock();
+    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
+
+    $this->_response = json_encode(['requestId' => 'xxx',
+                                    'returnStatus' => 'OK',
+                                    'returnMessage' => []]);
+
+    $this->book = (new DilicomFixtures())->albumTotemThora();
+  }
+
+
+  public function tearDown() {
+    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
+    RessourcesNumeriquesFixtures::deactivateDilicom();
+    parent::tearDown();
+  }
+}
+
+
+
+
+class PnbDilicomHubDeclareIpTest extends PnbDilicomHubTestCase {
+
+
+  /** @test */
+  public function savingDilicomPnbIPAddressShouldCallWebserviceDeclareIP() {
+    $this->_http->whenCalled('open_url')
+                ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/declareIp?login=afi-bib&password=secretPassword&glnContractor=123456789&ips%5B0%5D=10.12.13.24',
+                       ['auth' => [ 'user' => 'afi-bib',
+                                   'password' => 'secretPassword']])
+                ->answers($this->_response)
+                ->beStrict();
+
+    $this->fixture('Class_AdminVar',
+                   ['id' => 'DILICOM_PNB_IP_ADRESSES',
+                    'valeur' => '10.12.13.24']);
+
+    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
+  }
+
+
+  /** @test */
+  public function savingDilicomPnbIpAdressWithMultipleIpsShouldCallWebServiceDeclareIp() {
+    $this->_http->whenCalled('open_url')
+                ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/declareIp?login=afi-bib&password=secretPassword&glnContractor=123456789&ips%5B0%5D=10.12.13.24&ips%5B1%5D=11.128.16.87',
+                       ['auth' => [ 'user' => 'afi-bib',
+                                   'password' => 'secretPassword']])
+                ->answers($this->_response)
+                ->beStrict();
+
+    $this->fixture('Class_AdminVar',
+                   ['id' => 'DILICOM_PNB_IP_ADRESSES',
+                    'valeur' => '10.12.13.24;11.128.16.87']);
+
+    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
+  }
+}
+
+
+
+
+class PnbDilicomHubUpdateStatusSuccessfulTest extends PnbDilicomHubTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_wrapper = Storm_Test_ObjectWrapper::onLoaderOfModel('Class_Album_Item');
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse())
+      ->beStrict();
+
+    (new Class_WebService_BibNumerique_Dilicom_Hub())->updateStatus($this->book);
+    $this->item = $this->book->getItems()[0];
+  }
+
+
+  /** @test */
+  public function numberOfSimultaneousLoansRemaningShouldBeFiveOnSuccessfulUpddate() {
+    $this->assertEquals(5, $this->item->numberOfSimultaneousLoansRemaning());
+  }
+
+
+  /** @test */
+  public function loanQuantityRemainingShouldBe24OnSuccessfulUpdate() {
+    $this->assertEquals(24, $this->item->quantityOfLoansRemaining());
+  }
+
+
+  /** @test */
+  public function itemSaveShouldHaveBeenCalled() {
+    $this->assertTrue($this->_wrapper->methodHasBeenCalled('save'));
+  }
+}
+
+
+
+
+class PnbDilicomHubUpdateStatusErrorTest extends PnbDilicomHubTestCase {
+
+
+  /** @test */
+  public function numberOfSimultaneousLoansRemaningShouldNotHaveBeenUpdatedOnError() {
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusErrorResponse())
+      ->beStrict();
+
+    (new Class_WebService_BibNumerique_Dilicom_Hub())->updateStatus($this->book);
+
+    $this->assertEquals(48,
+                        $this->book->getItems()[0]->numberOfSimultaneousLoansRemaning());
+  }
+}
+
+
+
+
+class PnbDilicomUpdateLoansReturnDateTest extends PnbDilicomHubTestCase {
+
+  protected
+    $_loans,
+    $_first_request,
+    $_second_request;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    Storm_Cache::beVolatile();
+
+    $this->_loans = array_map(function($i)
+                              {
+                                return
+                                  $this->fixture('Class_Loan_Pnb',
+                                                 ['id' => $i,
+                                                  'album' => $this->fixture('Class_Album',
+                                                                            ['id' => $i,
+                                                                             'titre' => 'Dr House',
+                                                                             'id_origine' => 'Dilicom-' . $i])
+                                                 ]);
+                              },
+                              range(1, 35));
+
+    $this->_first_request = 'https://pnb-test.centprod.com/v3/pnb-numerique/json/getEndedLoans?login=afi-bib&password=secretPassword&glnContractor=123456789&loanId%5B0%5D=1&loanId%5B1%5D=2&loanId%5B2%5D=3&loanId%5B3%5D=4&loanId%5B4%5D=5&loanId%5B5%5D=6&loanId%5B6%5D=7&loanId%5B7%5D=8&loanId%5B8%5D=9&loanId%5B9%5D=10&loanId%5B10%5D=11&loanId%5B11%5D=12&loanId%5B12%5D=13&loanId%5B13%5D=14&loanId%5B14%5D=15&loanId%5B15%5D=16&loanId%5B16%5D=17&loanId%5B17%5D=18&loanId%5B18%5D=19&loanId%5B19%5D=20';
+
+    $this->_second_request = 'https://pnb-test.centprod.com/v3/pnb-numerique/json/getEndedLoans?login=afi-bib&password=secretPassword&glnContractor=123456789&loanId%5B0%5D=21&loanId%5B1%5D=22&loanId%5B2%5D=23&loanId%5B3%5D=24&loanId%5B4%5D=25&loanId%5B5%5D=26&loanId%5B6%5D=27&loanId%5B7%5D=28&loanId%5B8%5D=29&loanId%5B9%5D=30&loanId%5B10%5D=31&loanId%5B11%5D=32&loanId%5B12%5D=33&loanId%5B13%5D=34&loanId%5B14%5D=35';
+
+    $this->_http
+      ->whenCalled('open_url')->with($this->_first_request,
+                                     ['auth' => [ 'user' => 'afi-bib',
+                                                 'password' => 'secretPassword']])->answers('')
+      ->whenCalled('open_url')->with($this->_second_request,
+                                     ['auth' => [ 'user' => 'afi-bib',
+                                                 'password' => 'secretPassword']])->answers('')->beStrict();
+
+    (new Class_WebService_BibNumerique_Dilicom_Hub())->updateLoansReturnDate($this->_loans);
+  }
+
+
+  /** @test */
+  public function httpRequestCountShouldBeTwo() {
+    $this->assertEquals(2, $this->_http->methodCallCount('open_url'));
+  }
+
+
+  /** @test */
+  public function secondCallOnUpdateLoansReturnDateShouldBeCached() {
+    (new Class_WebService_BibNumerique_Dilicom_Hub())->updateLoansReturnDate($this->_loans);
+    $this->assertEquals(2, $this->_http->methodCallCount('open_url'));
+  }
+}
+
+
+
+
+abstract class PnbDilicomOfferParserTestCase extends ModelTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_pnb_filename = 'full_pnb_666_20150220T150017Z.xml',
+    $_xml;
+
+
+  public function setUp() {
+    parent::setUp();
+    $xmlpath = realpath(__DIR__) . '/../../fixtures/' . $this->_pnb_filename;
+    $this->_xml = file_get_contents($xmlpath);
+
+    $this->_http_client = Storm_Test_ObjectWrapper::mock()
+      ->whenCalled('open_url')
+      ->answers('');
+
+    Class_WebService_Abstract::setDefaultHttpClient($this->_http_client);
+
+    Class_Album_UsageConstraint::setTimeSource(new TimeSourceForTest('2014-05-02 14:14:14'));
+  }
+
+
+  public function tearDown() {
+    Class_WebService_Abstract::setDefaultHttpClient(null);
+    parent::tearDown();
+  }
+}
+
+
+
+
+class PnbDilicomOffersParserTest extends PnbDilicomOfferParserTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_Album_UsageConstraints::setTimeSource(new TimeSourceForTest('2015-11-24 09:00:00'));
+    $this->_books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
+  }
+
+
+  /** @test */
+  public function numberOfBooksShouldBeThree() {
+    $this->assertEquals(3, count($this->_books));
+  }
+
+
+  /** @test */
+  public function firstAlbumShouldBePlusJamaisSansElle() {
+    $album = $this->_books[0]->import();
+    $this->assertEquals('Plus jamais sans elle', $album->getTitre());
+
+    return $album;
+  }
+
+
+  /** @test */
+  public function firstBookExternalURIShouldBeEdenLivres() {
+    $this->assertEquals('http://www.edenlivres.fr/p/23416', $this->_books[0]->getExternalUri());
+  }
+
+
+  /** @test */
+  public function firstBookPostersShouldContainsFrontCoverDotJpg() {
+    $this->assertContains('https://assets.edenlivres.fr/assets/publications/23416/medias/front_cover.jpg',
+                          $this->_books[0]->getPosters());
+  }
+
+
+  /** @test */
+  public function firstBookPostersShouldContainsFrontCoverSmallDotJpg() {
+    $this->assertContains('http://assets.edenlivres.fr/assets/publications/23416/cover/small.jpg',
+                          $this->_books[0]->getPosters());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldBePlusJamaisSansElle
+   * @test
+   */
+  public function firstAlbumThumbnailShouldBeSmallDotJpg($album) {
+    $this->assertEquals('http://assets.edenlivres.fr/assets/publications/23416/cover/small.jpg',
+                        $album->getPoster());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldBePlusJamaisSansElle
+   * @test
+   */
+  public function firstAlbumShouldHaveTwoItems($album) {
+    $this->assertCount(2, $album->getItems());
+    return $album->getItems()[0];
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function firstAlbumFirstItemShouldHaveSixUsageConstraints($item) {
+    $this->assertCount(6, $item->getUsageConstraints());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function firstAlbumFirstItemItemLoanDurationShouldBe59($item) {
+    $this->assertEquals(59, $item->getUsageConstraints()->getLoanDuration());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function firstAlbumFirstItemLoanOrderLineIdShouldBe54e748d8975a2fa6aa4d3e25($item) {
+    $this->assertEquals('54e7473f975a2fa6aa4d3e17',
+                        $item->getUsageConstraints()->getLoanOrderLineId());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function firstAlbumFirstItemLoanOrderDateShouldBe2015_02_20($item) {
+    $this->assertEquals('2015-02-20T15:40:01.866+01:00',
+                        $item->getUsageConstraints()->getLoanOrderDate());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function firstAlbumFirstItemLoanQuantityShouldBe40($item) {
+    $this->assertEquals(40, $item->getUsageConstraints()->getLoanQuantity());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function firstAlbumFirstItemAvailabilityDurationShouldBe1825($item) {
+    $this->assertEquals(1825, $item->getUsageConstraints()->getAvailabilityDuration());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function firstAlbumFirstItemAvailabilityEndDateBeShouldBe2020_19_02($item) {
+    $this->assertEquals('2020-02-19T15:40:01+0100', $item->getUsageConstraints()->getAvailabilityEndDate());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function firstAlbumFirstItemLoanMaxNumberOfUsersShouldBe15($item) {
+    $this->assertEquals(15,
+                        $item->getUsageConstraints()->getLoanMaxNumberOfUsers());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldHaveTwoItems
+   * @test
+   */
+  public function albumLicenseShouldBeAvailableFor1548Days($item) {
+    $this->assertEquals(1548,
+                        $item->getUsageConstraints()->getAvailabilityRemainingDaysBeforeEndDate());
+  }
+
+
+  /** @test */
+  public function secondAlbumShouldBeJournalDunDegonfle() {
+    $album = $this->_books[1]->import();
+    $this->assertEquals('Journal d\'un dégonflé, t.1', $album->getTitre());
+    return $album;
+  }
+
+
+  /**
+   * @depends secondAlbumShouldBeJournalDunDegonfle
+   * @test
+   */
+  public function secondAlbumSubtitleShouldBeCarnetDeBordDeGregHeffley($album) {
+    $this->assertEquals('Carnet de bord de Greg Heffley', $album->getSousTitre());
+  }
+
+
+  /**
+   * @test
+   */
+  public function secondBookIdShouldBeDilicom9791023501735() {
+    $this->assertEquals('Dilicom-9791023501735', $this->_books[1]->getId());
+  }
+
+
+  /**
+   * @depends secondAlbumShouldBeJournalDunDegonfle
+   * @test
+   */
+  public function secondAlbumUrlOriginShouldBeDilicom($album) {
+    $this->assertEquals('http://www.dilicom.net', $album->getUrlOrigine());
+  }
+
+
+  /** @test */
+  public function thirdAlbumShouldBeHomerEtLeChienFormidable() {
+    $album = $this->_books[2]->import();
+    $this->assertEquals('Homer et le chien formidable', $album->getTitre());
+    return $album;
+  }
+
+
+
+  /**
+   * @depends thirdAlbumShouldBeHomerEtLeChienFormidable
+   * @test
+   */
+  public function thirdAlbumISBNShouldBe9791023504149($album) {
+    $this->assertEquals('9791023504149', $album->getIsbn());
+  }
+
+
+
+  /**
+   * @depends firstAlbumShouldBePlusJamaisSansElle
+   * @test
+   */
+  public function removeUsageConstraintsAndAskForLoanQuantityShouldAnswers0($item) {
+    $item->setUsageConstraints(new Class_Album_UsageConstraints());
+    $this->assertEquals(0, $item->getUsageConstraints()->getLoanQuantity());
+  }
+
+
+  /**
+   * @depends firstAlbumShouldBePlusJamaisSansElle
+   * @test
+   */
+  public function removeUsageConstraintsAndAskForValidityOfferShouldReturnFalse($item) {
+    $item->setUsageConstraints(new Class_Album_UsageConstraints());
+    $this->assertFalse($item->getUsageConstraints()->isAValidOffer());
+  }
+}
+
+
+
+
+class PnbDilicomOffersParserWithLoanDurationVariableTest extends PnbDilicomOfferParserTestCase {
+
+  protected $_item;
+
+
+  public function setUp() {
+    parent::setUp();
+    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', '21');
+
+    $this->_books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
+    $this->_item = $this->_books[0]->import()->getItems()[0];
+  }
+
+
+  /** @test */
+  public function firstAlbumLoanDurationShouldBe21() {
+    $this->assertEquals(21,
+                        $this->_item->getUsageConstraints()->getLoanDuration());
+  }
+
+
+  /** @test */
+  public function firstAlbumAvailabilityEndDateBeShouldBe2020_02_19() {
+    $album = $this->_books[0]->import();
+    $this->assertEquals('2020-02-19T15:40:01+0100',
+                        $this->_item->getUsageConstraints()->getAvailabilityEndDate());
+  }
+}
+
+
+
+
+class PnbDilicomOffersParserUpdateWithNewOrderTest extends PnbDilicomOfferParserTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    Class_Album_UsageConstraints::setTimeSource(new TimeSourceForTest('2015-03-31 09:00:00'));
+    $books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
+
+    foreach($books as $book)
+      $book->import();
+
+    $xmlpath = realpath(__DIR__) . '/../../fixtures/partial_pnb_666_20150220T150017Z.xml';
+
+    $this->_books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML(file_get_contents($xmlpath));
+
+    foreach($this->_books as $book)
+      $book->import();
+
+    Class_Album::clearCache();
+
+    $this->_items = Class_Album::findFirstBy(['id_origine' => 'Dilicom-9791023504148'])->getItems();
+  }
+
+
+  /** @test */
+  public function albumHomerShouldHaveTwoItems() {
+    $this->assertCount(2, $this->_items);
+  }
+
+
+  /** @test */
+  public function firstItemOrderLineIdShouldBe54e74() {
+    $this->assertEquals('54e748d8975a2fa6aa4d3e25', $this->_items[0]->getOrderLineId());
+  }
+
+
+  /** @test */
+  public function secondItemOrderLineIdShouldBe1234() {
+    $this->assertEquals('123456789abcde',
+                        $this->_items[1]->getOrderLineId());
+  }
+
+
+  /** @test */
+  public function secondItemAvailabilityDurationShouldBe1000() {
+    $this->assertEquals(1000,
+                        $this->_items[1]->getUsageConstraints()->getAvailabilityDuration());
+  }
+}
+
+
+
+
+class PnbDilicomDecitresOffersParserTest extends PnbDilicomOfferParserTestCase {
+
+  protected $_pnb_filename = 'full_pnb_decitres.xml';
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->_books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
+  }
+
+
+  /** @test */
+  public function numberOfBooksShouldBeFour() {
+    $this->assertEquals(4, count($this->_books));
+  }
+
+
+  /** @test */
+  public function firstBookShouldBeEspritRouge() {
+    $album = $this->_books[0]->import();
+    $this->assertEquals('L’Esprit rouge', $album->getTitre());
+
+    return $album;
+  }
+
+
+  /**
+   * @depends firstBookShouldBeEspritRouge
+   * @test
+   */
+  public function firstBookThumbnailShouldBeSmallDotJpg($album) {
+    $this->assertEquals('https://assets.edenlivres.fr/assets/publications/171925/cover/small.jpg',
+                        $album->getPoster());
+  }
+
+
+  /** @test */
+  public function secondBookShouldBeCompagnons() {
+    $album = $this->_books[1]->import();
+    $this->assertEquals('Compagnons', $album->getTitre());
+
+    return $album;
+  }
+
+
+  /**
+   * @depends secondBookShouldBeCompagnons
+   * @test
+   */
+  public function secondBookDescriptionShouldContainsJeuneApprentie($album) {
+    $this->assertContains('Émilie, jeune apprentie au caractère entier',
+                          $album->getDescription());
+  }
+}
+
+
+
+
+class PnbDilicomRiviereEsperanceOffersParserTest extends PnbDilicomOfferParserTestCase {
+
+  protected
+    $_pnb_filename = 'riviere_esperance.xml',
+    $_item;
+
+
+  public function setUp() {
+    parent::setUp();
+    $books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
+    $books[0]->import();
+    $this->_item = Class_Album_Item::find(1);
+  }
+
+
+  /** @test */
+  public function itemLoanMaxNumberOfUsersShouldBe30() {
+    $this->assertEquals(30,
+                        $this->_item->getUsageConstraints()->getLoanMaxNumberOfUsers());
+  }
+
+
+  /** @test */
+  public function itemLoanQuantityShouldBe50() {
+    $this->assertEquals(50,
+                        $this->_item->getUsageConstraints()->getLoanQuantity());
+  }
+}
+
+
+
+
+class PnbDilicomProduitsTerroirOffersParserTest extends PnbDilicomOfferParserTestCase {
+
+  protected
+    $_pnb_filename = 'produits_terroir.xml',
+    $_item;
+
+
+  public function setUp() {
+    parent::setUp();
+    $books = Class_WebService_BibNumerique_Dilicom_PNBOffersFile::booksFromXML($this->_xml);
+    $books[0]->import();
+    $this->_item = Class_Album_Item::find(1);
+  }
+
+
+  /** @test */
+  public function itemLoanDurationShouldBe59() {
+    $this->assertEquals(59,
+                        $this->_item->getUsageConstraints()->getLoanDuration());
+  }
+}
+
+
+
+
+class PnbDilicomONIXParserTest extends ModelTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    $xmlpath = realpath(__DIR__) . '/../../fixtures/la_mer.xml';
+    $xml = file_get_contents($xmlpath);
+
+    Storm_Model_Loader::defaultToVolatile();
+    $this->_book = Class_WebService_BibNumerique_Dilicom_ONIXFile::bookFromXML($xml);
+  }
+
+
+  public function tearDown() {
+    Storm_Model_Loader::defaultToDb();
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function bookShouldNotBeNull() {
+    $this->assertNotNull($this->_book);
+  }
+
+
+  /** @test */
+  public function titleShouldBeCertainesNAvaientJamaisVuLaMer() {
+    $this->assertEquals('Certaines n\'avaient jamais vu la mer', $this->_book->getTitle());
+  }
+
+
+  /** @test */
+  public function isbnShouldBe9782752908643() {
+    $this->assertEquals('9782752908643', $this->_book->getIsbn());
+  }
+
+
+  /** @test */
+  public function mainAuthorShouldBeJulieOtsuka() {
+    $this->assertEquals('Julie Otsuka', $this->_book->getAuthors()[0]);
+  }
+
+
+  /** @test */
+  public function publisherShouldBePhebus() {
+    $this->assertEquals('Phébus', $this->_book->getEditeur());
+  }
+
+
+  /** @test */
+  public function publishingDateShouldBe2012() {
+    $this->assertEquals(2012, $this->_book->getYear());
+  }
+
+
+  /** @test */
+  public function descriptionShouldContainsPrixFemina() {
+    $this->assertContains('Prix Femina étranger 2012', $this->_book->getDescription());
+  }
+
+
+  /** @test */
+  public function languageShouldBeFRE() {
+    $this->assertEquals('fre', $this->_book->getIdLangue());
+  }
+
+
+  /** @test */
+  public function importAlbumTypeDocShouldBeDILICOM() {
+    $album = $this->_book->import();
+    $this->assertEquals(Class_TypeDoc::DILICOM, $album->getTypeDocId());
+    return $album;
+  }
+
+
+  /** @test */
+  public function albumRightShouldBeOther() {
+    $album = $this->_book->import();
+    $this->assertEquals('Tous droits réservés', $album->getDroits());
+  }
+
+
+  /**
+   * @depends importAlbumTypeDocShouldBeDILICOM
+   * @test
+   */
+  public function albumFieldTenDollarAShouldBeISBN9782752908643($album) {
+    $this->assertEquals('9782752908643', $album->getMarc()->getSubfield('10', 'a')[0]);
+  }
+
+
+  /**
+   * @depends importAlbumTypeDocShouldBeDILICOM
+   * @test
+   */
+  public function albumMainAuthorShouldBeJulieOtsuka($album) {
+    $this->assertEquals('Julie Otsuka', $album->getMainAuthorName());
+  }
+
+
+  /**
+   * @depends importAlbumTypeDocShouldBeDILICOM
+   * @test
+   */
+  public function albumPublisherShouldContainsPhebus($album) {
+    $this->assertContains('Phébus', $album->getEditors());
+  }
+
+
+  /**
+   * @depends importAlbumTypeDocShouldBeDILICOM
+   * @test
+   */
+  public function albumCollectionShouldContainsLitteratureEtrangere($album) {
+    $this->assertContains('Littérature étrangère', $album->getCollections());
+  }
+
+
+  /**
+   * @depends importAlbumTypeDocShouldBeDILICOM
+   * @test
+   */
+  public function albumCategoryShouldBeLivreNumeriquePNB($album) {
+    $this->assertEquals('Livre numérique (PNB)', $album->getCategorie()->getLibelle());
+  }
+
+}
+
+
+
+
+abstract class PnbDilicomBatchTestCase extends ModelTestCase {
+
+  protected
+    $_storm_default_to_volatile = true,
+    $_log = '',
+    $_debug_log = '';
+
+
+  public function setUp() {
+    parent::setUp();
+    Storm_Cache::beVolatile();
+  }
+
+
+  public function getLogger() {
+    return $this->logger = $this->mock()
+                                ->whenCalled('log')
+                                ->willDo(
+                                         function($message, $target = '') {
+                                           if ($target == 'debug') {
+                                             $this->_debug_log .= $message . "\n";
+                                             return;
+                                           }
+                                           $this->_log .= $message;
+                                         });
+  }
+
+
+  protected function _enableDilicom() {
+    RessourcesNumeriquesFixtures::activateDilicom();
+  }
+
+
+  protected function _prepareTotemThora() {
+    return (new DilicomFixtures())->albumTotemThora();
+  }
+}
+
+
+
+
+class Class_Batch_DilicomSimpleTest extends PnbDilicomBatchTestCase {
+
+  protected $_batch, $_jobs;
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->_jobs = $this->mock()
+                        ->whenCalled('run')->answers(null)
+                        ->whenCalled('setLogger')->with(null)->answers(null)
+                        ->beStrict();
+
+    $this->_batch = (new Class_Batch_Dilicom)
+      ->setJobs($this->_jobs);
+  }
+
+
+  /** @test */
+  public function labelShouldBePnbDilicom() {
+    $this->assertEquals('PNB Dilicom', $this->_batch->getLabel());
+  }
+
+
+  /** @test */
+  public function withoutVarsShouldNotBeEnabled() {
+    $this->assertFalse($this->_batch->isEnabled());
+  }
+
+
+  /** @test */
+  public function withVarsShouldBeEnabled() {
+    $this->_enableDilicom();
+    $this->assertTrue($this->_batch->isEnabled());
+  }
+
+
+  /** @test */
+  public function runShouldRunJobs() {
+    $this->_batch->run();
+    $this->assertTrue($this->_jobs->methodHasBeenCalled('run'));
+  }
+
+
+  /** @test */
+  public function settingLoggerShouldSetJobsLogger() {
+    $this->_batch->setLogger(null);
+    $this->assertTrue($this->_jobs->methodHasBeenCalled('setLogger'));
+  }
+}
+
+
+
+
+abstract class PnbDilicomJobOnixTestCase extends PnbDilicomBatchTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_AdminVar',
+                   ['id' => 'DILICOM_PNB_FTP_SERVER',
+                    'clef' => 'DILICOM_PNB_FTP_SERVER',
+                    'valeur' => 'pftp.centprod.com']);
+
+    $this->fixture('Class_AdminVar',
+                   ['id' => 'DILICOM_PNB_FTP_USER',
+                    'clef' => 'DILICOM_PNB_FTP_USER',
+                    'valeur' => '007']);
+
+    $this->fixture('Class_AdminVar',
+                   ['id' => 'DILICOM_PNB_FTP_PASS',
+                    'clef' => 'DILICOM_PNB_FTP_PASS',
+                    'valeur' => 'IGotZeLicense']);
+
+    $this->_prepareFixtures();
+
+
+    $file_system = $this->mock()
+                        ->whenCalled('file_get_contents')
+                        ->with(PATH_TEMP . 'dilicom/full_pnb_3056309900005_20150726T050431Z.xml')
+                        ->answers(file_get_contents(__DIR__ . '/../../fixtures/full_pnb_3056309900005_20150726T050431Z.xml'))
+
+                        ->whenCalled('file_get_contents')
+                        ->answers(file_get_contents(__DIR__ . '/../../fixtures/diffusion_pnb_3056309900005_20150728T050431Z.xml'))
+                        ->whenCalled('file_exists')
+                        ->with(PATH_TEMP . 'dilicom/')
+                        ->answers(true);
+
+    Class_Batch_DilicomJobOnix::setFileSystem($file_system);
+
+    Class_WebService_BibNumerique_RessourceNumerique::setLogger($this->getLogger());
+
+    (new Class_Batch_DilicomJobOnix(new Class_Batch_Dilicom))
+      ->setFtpClient($this->getFtpClient())
+      ->setLogger($this->getLogger())
+      ->run();
+  }
+
+
+  public function tearDown() {
+    Class_WebService_BibNumerique_RessourceNumerique::setLogger(null);
+    parent::tearDown();
+  }
+
+
+  protected function _prepareFixtures() { }
+
+
+  public function getFtpClient() {
+    return $this->mock();
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobOnixNoConnectionTest extends PnbDilicomJobOnixTestCase {
+
+  public function getFtpClient() {
+    return parent::getFtpClient()
+      ->whenCalled('connect')
+      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
+      ->answers(false)
+
+      ->beStrict();
+  }
+
+
+  /** @test */
+  public function noConnectionErrorShouldBeLogged() {
+    $this->assertContains('Impossible de se connecter au serveur pftp.centprod.com',
+                          $this->_log);
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobOnixNoLsTest extends PnbDilicomJobOnixTestCase {
+
+  public function getFtpClient() {
+    return parent::getFtpClient()
+      ->whenCalled('connect')
+      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
+      ->answers(true)
+      ->whenCalled('ls')
+      ->with('HUB/O/')
+      ->answers(false)
+
+      ->beStrict();
+  }
+
+
+  /** @test */
+  public function noLsErrorShouldBeLogged() {
+    $this->assertContains('Impossible de lister le contenu de HUB/O/',
+                          $this->_log);
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobOnixNoGetTest extends PnbDilicomJobOnixTestCase {
+
+  public function getFtpClient() {
+    return parent::getFtpClient()
+      ->whenCalled('connect')
+      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
+      ->answers(true)
+      ->whenCalled('ls')
+      ->with('HUB/O/')
+      ->answers(['full_pnb_3056309900005_20150726T050431Z.xml',
+                 'full_pnb_3056309900005_20150719T050431Z.xml',
+                 'diffusion_pnb_3056309900005_20150720T050431Z.xml',
+                 'diffusion_pnb_3056309900005_20150728T050431Z.xml',
+                 '007.xml'])
+
+      ->whenCalled('get')
+      ->with('HUB/O/full_pnb_3056309900005_20150726T050431Z.xml',
+             PATH_TEMP . 'dilicom/full_pnb_3056309900005_20150726T050431Z.xml')
+      ->answers(false)
+      ->beStrict();
+  }
+
+
+  /** @test */
+  public function noGetErrorShouldBeLogged() {
+    $this->assertContains('Impossible de télécharger le fichier HUB/O/full_pnb_3056309900005_20150726T050431Z.xml',
+                          $this->_log);
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobOnixWithFullTest extends PnbDilicomJobOnixTestCase {
+
+  public function getFtpClient() {
+    return parent::getFtpClient()
+      ->whenCalled('connect')
+      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
+      ->answers(true)
+
+      ->whenCalled('ls')
+      ->with('HUB/O/')
+      ->answers(['full_pnb_3056309900005_20150726T050431Z.xml',
+                 'full_pnb_3056309900005_20150719T050431Z.xml',
+                 'diffusion_pnb_3056309900005_20150720T050431Z.xml',
+                 'diffusion_pnb_3056309900005_20150728T050431Z.xml',
+                 '007.xml'])
+
+      ->whenCalled('get')
+      ->with('HUB/O/full_pnb_3056309900005_20150726T050431Z.xml',
+             PATH_TEMP . 'dilicom/full_pnb_3056309900005_20150726T050431Z.xml')
+      ->answers(true)
+
+      ->whenCalled('get')
+      ->with('HUB/O/diffusion_pnb_3056309900005_20150728T050431Z.xml',
+             PATH_TEMP . 'dilicom/diffusion_pnb_3056309900005_20150728T050431Z.xml')
+      ->answers(true)
+
+      ->beStrict();
+  }
+
+
+  protected function _prepareFixtures() {
+    $this->fixture('Class_Batch',
+                   ['id' => 12,
+                    'type' => Class_Batch_Dilicom::TYPE,
+                    'data' => '']);
+  }
+
+
+  /** @test */
+  public function shouldLog4FilesInDirectory() {
+    $this->assertContains('4 fichier(s) à traiter dans HUB/O/', $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldLogFullFileToProcess() {
+    $this->assertContains('Fichier total à traiter : full_pnb_3056309900005_20150726T050431Z.xml',
+                          $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldLogIncrementalFileToProcess() {
+    $this->assertContains('Fichiers incrémentaux à traiter : diffusion_pnb_3056309900005_20150728T050431Z.xml',
+                          $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldHaveAdded3Albums() {
+    $this->assertEquals(3, Class_Album::countBy(['type_doc_id' => Class_TypeDoc::DILICOM]));
+  }
+
+
+  /** @test */
+  public function modelDataShouldContainsLastValidFileName() {
+    $this->assertEquals('diffusion_pnb_3056309900005_20150728T050431Z.xml',
+                        (new Class_Batch_Dilicom)->getModel()->getData());
+  }
+
+
+  /** @test */
+  public function debugLogShouldContainsImportLaReineDesNeiges() {
+    $this->assertContains('[Dilicom_Book] create "La Reine des Neiges" (Dilicom-9782290123409)',
+                          $this->_debug_log);
+
+    $this->assertContains('new album id: 1',
+                          $this->_debug_log);
+  }
+
+
+  /** @test */
+  public function dilicomItemIdOrigineShouldBeDilicom9782290123409() {
+    Class_Album::find(1)->index();
+    $this->assertNotNull(Class_Exemplaire::findFirstBy(['id_origine' => 'Dilicom-9782290123409']));
+    $this->assertNotNull(Class_Album::find(1)->getNotice());
+    $this->assertEquals('Dilicom-9782290123409', Class_Notice::find(1)->getAlbum()->getIdOrigine());
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobOnixWithDataTest extends PnbDilicomJobOnixTestCase {
+
+  public function getFtpClient() {
+    return parent::getFtpClient()
+      ->whenCalled('connect')
+      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
+      ->answers(true)
+
+      ->whenCalled('ls')
+      ->with('HUB/O/')
+      ->answers(['full_pnb_3056309900005_20150726T050431Z.xml',
+                 'full_pnb_3056309900005_20150719T050431Z.xml',
+                 'diffusion_pnb_3056309900005_20150720T050431Z.xml',
+                 'diffusion_pnb_3056309900005_20150728T050431Z.xml',
+                 '007.xml'])
+
+      ->whenCalled('get')
+      ->with('HUB/O/full_pnb_3056309900005_20150726T050431Z.xml',
+             PATH_TEMP . 'dilicom/full_pnb_3056309900005_20150726T050431Z.xml')
+      ->answers(true)
+
+      ->whenCalled('get')
+      ->with('HUB/O/diffusion_pnb_3056309900005_20150728T050431Z.xml',
+             PATH_TEMP . 'dilicom/diffusion_pnb_3056309900005_20150728T050431Z.xml')
+      ->answers(true)
+
+      ->beStrict();
+  }
+
+
+  protected function _prepareFixtures() {
+    $this->fixture('Class_Batch',
+                   ['id' => 12,
+                    'type' => Class_Batch_Dilicom::TYPE,
+                    'data' => 'full_pnb_3056309900005_20150822T050431Z.xml']);
+  }
+
+
+  /** @test */
+  public function shouldLogFullFileToProcess() {
+    $this->assertContains('Aucun fichier plus récent que le dernier fichier déjà traité (full_pnb_3056309900005_20150822T050431Z.xml)',
+                          $this->_log);
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobOnixWithoutFullTest extends PnbDilicomJobOnixTestCase {
+
+  public function getFtpClient() {
+    return parent::getFtpClient()
+      ->whenCalled('connect')
+      ->with('pftp.centprod.com', '007', 'IGotZeLicense')
+      ->answers(true)
+
+      ->whenCalled('ls')
+      ->with('HUB/O/')
+      ->answers(['diffusion_pnb_3056309900005_20150720T050431Z.xml',
+                 'diffusion_pnb_3056309900005_20150728T050431Z.xml',
+                 '007.xml'])
+
+      ->whenCalled('get')
+      ->with('HUB/O/diffusion_pnb_3056309900005_20150720T050431Z.xml',
+             PATH_TEMP . 'dilicom/diffusion_pnb_3056309900005_20150720T050431Z.xml')
+      ->answers(true)
+
+      ->whenCalled('get')
+      ->with('HUB/O/diffusion_pnb_3056309900005_20150728T050431Z.xml',
+             PATH_TEMP . 'dilicom/diffusion_pnb_3056309900005_20150728T050431Z.xml')
+      ->answers(true)
+
+      ->beStrict();
+  }
+
+
+  /** @test */
+  public function shouldLog2FilesInDirectory() {
+    $this->assertContains('2 fichier(s) à traiter dans HUB/O/', $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldNotLogFullFileToProcess() {
+    $this->assertNotContains('Fichier total', $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldLogIncrementalFileToProcess() {
+    $this->assertContains('Fichiers incrémentaux à traiter : diffusion_pnb_3056309900005_20150720T050431Z.xml diffusion_pnb_3056309900005_20150728T050431Z.xml',
+                          $this->_log);
+  }
+}
+
+
+
+
+abstract class PnbDilicomBatchJobEndedLoansTestCase extends PnbDilicomBatchTestCase {
+
+  protected $_batch,
+    $_http,
+    $_time_source;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_http = $this->mock()->beStrict();
+    $this->_http
+      ->whenCalled('setAuth')->with('afi-bib', 'secretPassword')->answers(null)
+      ->whenCalled('setAuth')->with(null)->answers(null);
+    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
+
+    $this->_time_source = new TimeSourceForTest('2015-10-01T18:06:18+0200');
+    Class_Loan_Pnb::setTimeSource($this->_time_source);
+
+    $this->_enableDilicom();
+
+    $this->_batch = (new Class_Batch_DilicomJobEndedLoans(new Class_Batch_Dilicom))
+      ->setLogger($this->getLogger())
+      ->setMemoryCleaner(function() {});
+  }
+
+
+  public function tearDown() {
+    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
+    Class_Loan_Pnb::setTimeSource(null);
+    parent::tearDown();
+  }
+
+
+  protected function _loadLoans() {
+    $loan = $this->fixture('Class_Loan_Pnb',
+                           ['id' => 12,
+                            'expected_return_date' => '2015-12-01 17:33:33']);
+
+    $this
+      ->onLoaderOfModel('Class_Loan_Pnb')
+      ->whenCalled('findAllBy')
+      ->with(['where' => '(id > 0) and expected_return_date > "2015-10-01 18:06:18"',
+              'limit' => 200,
+              'order' => 'id'])
+      ->willDo(function() use ($loan) { return [$loan]; })
+
+      ->whenCalled('findAllBy')
+      ->with(['where' => '(id > 12) and expected_return_date > "2015-10-01 18:06:18"',
+              'limit' => 200,
+              'order' => 'id'])
+      ->willDo(function() { return []; })
+
+      ->whenCalled('findAllBy')
+      ->willDo(
+               function() {
+                 $this->fail('Unexpected call to Class_Loan_Pnb::findAllBy() with params :' . json_encode(func_get_args()). ')');
+               })
+      ;
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobEndedLoansDisabledTest extends PnbDilicomBatchJobEndedLoansTestCase {
+
+  protected function _enableDilicom() {}
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->_batch->run();
+  }
+
+
+  /** @test */
+  public function shouldLogJobName() {
+    $this->assertContains('Vérification des prêts rendus de manière anticipée',
+                          $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldLogDisabledDilicom() {
+    $this->assertContains('PNB Dilicom désactivé', $this->_log);
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobEndedLoansNoLoansTest extends PnbDilicomBatchJobEndedLoansTestCase {
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->_batch->run();
+  }
+
+
+  /** @test */
+  public function shouldLogNothingToDo() {
+    $this->assertContains('Aucun prêt à vérifier', $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldNotLogUpdatedLoans() {
+    $this->assertNotContains('Aucun prêt rendu de manière anticipée', $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldLogNotCallDilicomHub() {
+    $this->assertFalse($this->_http->methodHasBeenCalled('open_url'));
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobEndedLoansNoUpdatesTest extends PnbDilicomBatchJobEndedLoansTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    Storm_Cache::beVolatile();
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getEndedLoans?login=afi-bib&password=secretPassword&glnContractor=123456789&loanId%5B0%5D=12',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getEndedLoansResponse());
+
+    $this->_loadLoans();
+    $this->_batch->run();
+  }
+
+
+  /** @test */
+  public function shouldLogOneLoanToCheck() {
+    $this->assertContains('1 prêt(s) à vérifier', $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldLogNoUpdate() {
+    $this->assertContains('Aucun prêt rendu de manière anticipée', $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldCallDilicomHub() {
+    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobEndedLoansWithUpdatesTest extends PnbDilicomBatchJobEndedLoansTestCase {
+
+  protected $_early_returned = '2015-09-01T17:33:33+02:00';
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getEndedLoans?login=afi-bib&password=secretPassword&glnContractor=123456789&loanId%5B0%5D=12',
+             ['auth' => ['user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getEndedLoansResponse(['{"loanId":"12","returnDate":"'. $this->_early_returned . '"}']));
+
+
+    $this->_loadLoans();
+    $this->_batch->run();
+  }
+
+
+  /** @test */
+  public function shouldLogOneLoanToCheck() {
+    $this->assertContains('1 prêt(s) à vérifier', $this->_log);
+  }
+
+
+  /** @test */
+  public function shouldCallDilicomHub() {
+    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
+  }
+
+
+  /** @test */
+  public function shouldLogLoanUpdated() {
+    $this->assertContains('1 prêt(s) rendu(s) de manière anticipée', $this->_log);
+  }
+
+
+  /** @test */
+  public function loanReturnDateShouldBeUpdated() {
+    $this->assertEquals($this->_early_returned,
+                        Class_Loan_Pnb::find(12)->getExpectedReturnDate());
+  }
+
+
+  /** @test */
+  public function zone995ShouldContainsDateAndCommandOrderAndBarcod() {
+    $book = $this->_prepareTotemThora()
+                 ->setVisible(true)
+                 ->setStatus(Class_Album::STATUS_VALIDATED);
+
+    $book->save();
+    $book->index();
+
+    $item = ($items = Class_Notice::find(1)->getExemplaires())
+      ? array_pop($items)
+      : new Class_Exemplaire();
+
+    $this->assertEquals('a:5:{i:0;a:2:{s:4:"code";s:1:"a";s:6:"valeur";s:0:"";}i:1;a:2:{s:4:"code";s:1:"c";s:6:"valeur";s:19:"2015-04-01 00:00:00";}i:2;a:2:{s:4:"code";s:1:"f";s:6:"valeur";s:16:"Dilicom-88817216";}i:3;a:2:{s:4:"code";s:1:"n";s:6:"valeur";s:4:"x321";}i:4;a:2:{s:4:"code";s:1:"v";s:6:"valeur";s:26:"A consulter sur le portail";}}', $item->getZone995());
+  }
+}
+
+
+
+
+class PnbDilicomBatchJobUnindexExpiredOrdersTest extends PnbDilicomBatchTestCase {
+
+  protected $_batch;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_time_source = new TimeSourceForTest('2015-10-02 14:14:14');
+    Class_Album_UsageConstraint::setTimeSource($this->_time_source);
+    Class_Album_UsageConstraints::setTimeSource($this->_time_source);
+
+    $book = $this->_prepareTotemThora();
+    $book
+      ->getItems()[0]
+      ->getUsageConstraints()
+      ->getAvailabilityConstraint()
+      ->setDuration(1);
+
+    $book
+      ->setVisible(true)
+      ->save();
+    $book->index();
+
+    $this->_enableDilicom();
+
+    $this->_batch = (new Class_Batch_DilicomJobUnindexExpiredOrders(new Class_Batch_Dilicom));
+
+    $this->_batch
+      ->setLogger($this->getLogger())
+      ->run();
+  }
+
+
+  /** @test */
+  public function albumShouldNotBeVisible() {
+    $this->assertFalse(Class_Album::find(3)->getVisible());
+  }
+
+
+  /** @test */
+  public function loggerShouldContainsVerificationDesCommandesExpirees() {
+    $this->assertContains('Vérification des commandes expirées',
+                          $this->_log);
+  }
+
+
+  /** @test */
+  public function albumShouldBeIndexed() {
+    $this->assertEmpty(Class_Notice::findAll());
+  }
+}
+
+
+
+
+abstract class PnbDilicomViewHelperNoticeTestCase extends ViewHelperTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    $this->_helper = new ZendAfi_View_Helper_Notice_Dilicom();
+    $this->_helper->setView(new ZendAfi_Controller_Action_Helper_View());
+    $this->_prepareFixture();
+    $this->_html = $this->_helper->Notice_Dilicom($this->_record);
+  }
+
+
+  protected function _prepareFixture() {
+    $this->_record = $this->fixture('Class_Notice',
+                                    ['id' => 1,
+                                     'type_doc' => Class_TypeDoc::DILICOM,
+                                     'clef_alpha' => 'POMMEETANANAS',
+                                     'alpha_titre' => 'POM POMS ANANA ANANAS',
+                                     'alpha_auteur' => 'AUTEUR AUTEURS',
+                                     'unimarc' => '']);
+  }
+}
+
+
+
+
+class PnbDilicomViewHelperNoticeNotLoggedTest extends PnbDilicomViewHelperNoticeTestCase {
+
+  /** @test */
+  public function shouldRenderLoginForm() {
+    $this->assertXPath($this->_html, '//form', $this->_html);
+  }
+}
+
+
+
+
+class PnbDilicomViewHelperNoticeLoggedTest extends PnbDilicomViewHelperNoticeTestCase {
+
+  protected function _prepareFixture() {
+    parent::_prepareFixture();
+    $user = $this->fixture('Class_Users',
+                           ['id' => 6,
+                            'login' => 'admin',
+                            'password' => 'admin',
+                            'role_level' => ZendAfi_Acl_AdminControllerRoles::ADMIN_PORTAIL]);
+
+    ZendAfi_Auth::getInstance()->logUser($user);
+
+    $this->fixture('Class_Album',
+                   ['id' => 2,
+                    'titre' => 'Musso Dilicom',
+                    'type_doc_id' => Class_TypeDoc::DILICOM,
+                    'id_origine' => 'dilicom-123456',
+                    'url_origine' => 'https://url_dilicom.org/ressource/id/1',
+                    'notice_id' => 1]);
+
+    $this->fixture('Class_Exemplaire',
+                   ['id' => 564,
+                    'id_notice' => 1,
+                    'id_origine' => 'dilicom-123456']);
+  }
+
+
+  /** @test */
+  public function shouldRenderDilicomAlbumDatas() {
+    $this->assertXPathContentContains($this->_html, '//table//td', 'dilicom-123456');
+  }
+}
+
+
+
+
+abstract class PnbDilicomViewHelperRenderAlbumTestCase extends ViewHelperTestCase {
+
+  protected $_storm_default_to_volatile = true,
+    $_helper,
+    $_html;
+
+  public function setUp() {
+    parent::setUp();
+
+    $view = new ZendAfi_Controller_Action_Helper_View();
+    $this->_helper = new ZendAfi_View_Helper_RenderAlbum();
+    $this->_helper->setView($view);
+
+    $this->book = (new DilicomFixtures())->albumTotemThora();
+    RessourcesNumeriquesFixtures::activateDilicom();
+
+    $this->_http = Storm_Test_ObjectWrapper::mock();
+    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
+
+    $this->_time_source = new TimeSourceForTest('2014-05-02 14:14:14');
+    Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource($this->_time_source);
+    Class_Album_UsageConstraint::setTimeSource($this->_time_source);
+
+
+    $this->_http
+      ->whenCalled('setAuth')
+      ->answers(null)
+
+      ->whenCalled('open_url')
+      ->answers(DilicomFixtures::getLoanStatusResponse());
+
+  }
+
+
+  public function tearDown() {
+    ZendAfi_Auth::getInstance()->clearIdentity();
+    parent::tearDown();
+  }
+}
+
+
+
+
+class PnbDilicomViewHelperRenderAlbumPNBNotLoggedTest extends PnbDilicomViewHelperRenderAlbumTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    ZendAfi_Auth::getInstance()->clearIdentity();
+    $this->_html = $this->_helper->renderAlbum($this->book);
+  }
+
+
+  /** @test */
+  public function htmlShouldNotContainsLinkToConsultBook() {
+    $this->assertXPathContentContains($this->_html,
+                                      '//a[contains(@href, "/bib-numerique/consult-book-ajax/id/3")]',
+                                      'Consulter le livre en ligne');
+  }
+}
+
+
+
+
+class PnbDilicomViewHelperRenderAlbumPNBLoggedButNotAuthorizeTest extends PnbDilicomViewHelperRenderAlbumTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->logged_user = $this->fixture('Class_Users',
+                                        ['id' => 6,
+                                         'nom'=>'Pito',
+                                         'login'=>'Chat',
+                                         'password'=>'123456',
+                                         'id_site' => 1,
+                                         'idabon' => '12345']);
+
+    $this->logged_user->setUserGroups([]);
+    ZendAfi_Auth::getInstance()->logUser($this->logged_user);
+    $this->_html = $this->_helper->renderAlbum($this->book);
+  }
+
+
+  /** @test */
+  public function htmlShouldNotContainsLinkToConsultBook() {
+    $this->assertXPathContentContains($this->_html,
+                                      '//p',utf8_encode("Vous n'avez pas le droit d'accéder à la consultation en ligne."));
+  }
+}
+
+
+
+
+class PnbDilicomViewHelperRenderAlbumPNBTest extends PnbDilicomViewHelperRenderAlbumTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Bib', ['id' => 1,
+                                 'libelle' => 'Annecy',
+                                 'gln' => '333']);
+
+    $this->logged_user = $this->fixture('Class_Users',
+                                        ['id' => 6,
+                                         'nom'=>'Pito',
+                                         'login'=>'Chat',
+                                         'password'=>'123456',
+                                         'id_site' => 1,
+                                         'idabon' => '12345',
+                                         'user_groups' => [$this->fixture('Class_UserGroup',
+                                                                          ['id' => '20',
+                                                                           'libelle' => 'Multimedia',
+                                                                           'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]])]]);
+
+    $this->logged_user->beAbonneSIGB()->assertSave();
+    ZendAfi_Auth::getInstance()->logUser($this->logged_user);
+
+    $this->fixture('Class_Loan_Pnb', ['id' => 1,
+                                      'record_origin_id' => 'Dilicom-88817216',
+                                      'user_id' => '6']);
+
+    $this->_html = $this->_helper->renderAlbum($this->book);
+  }
+
+
+  public function tearDown() {
+    unset($_SERVER['HTTPS']);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function htmlShouldContainsIFrameOnEdenBook() {
+    $this->assertXPath($this->_html,
+                       '//iframe[@src="http://www.edenlivres.fr/p/23416"][@width="100%"][@height="600px"]',
+                       $this->_html);
+  }
+
+
+  /** @test */
+  public function htmlShouldContainsLinkToConsultBook() {
+    $this->assertXPathContentContains($this->_html,
+                                      '//a[contains(@href, "/bib-numerique/consult-book-ajax/id/3")]',
+                                      'Consulter le livre en ligne');
+  }
+
+
+  /** @test */
+  public function htmlShouldContainsLinkToLoanBook() {
+    $this->assertXPathContentContains($this->_html,
+                                      '//a[contains(@href, "/bib-numerique/loan-book-ajax/id/3")]',
+                                      'Emprunter le livre au format EPUB');
+  }
+
+
+  /** @test */
+  public function bibWithoutGlnShouldReturnErrorMessage() {
+    Class_Bib::find(1)->setGln('')->assertSave();
+    $this->_html = $this->_helper->renderAlbum($this->book);
+    $this->assertXPathContentContains($this->_html,
+                                      '//p', utf8_encode('Annecy n\'a pas accès à la consultation en ligne'));
+  }
+
+
+  /** @test */
+  public function userWithoutBibShouldReturnErrorMessage() {
+    $this->logged_user->setBib(null)->save();
+    $this->_html = $this->_helper->renderAlbum($this->book);
+    $this->assertXPathContentContains($this->_html,
+                                      '//p', utf8_encode('Vous devez être inscrit dans une bibliothèque pour accéder à la consultation en ligne'));
+
+  }
+
+  /** @test */
+  public function withHttpsPreviewSrcShouldBeHttps() {
+    $_SERVER['HTTPS'] = 'on';
+
+    $this->_html = $this->_helper->renderAlbum($this->book);
+    $this->assertXPath($this->_html,
+                       '//iframe[@src="https://www.edenlivres.fr/p/23416"][@width="100%"][@height="600px"]',
+                       $this->_html);
+  }
+}
+
+
+
+
+class PnbDilicomViewHelperRenderAlbumPNBGetLoanStatusTest extends PnbDilicomViewHelperRenderAlbumTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->fixture('Class_Bib', ['id' => 1,
+                                 'libelle' => 'Annecy',
+                                 'gln' => '333']);
+
+    $group =$this->fixture('Class_UserGroup',
+                           ['id' => '20',
+                            'libelle' => 'Multimedia',
+                            'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]]);
+
+    $this->logged_user = $this->fixture('Class_Users',
+                                        ['id' => 6,
+                                         'nom'=>'Pito',
+                                         'login'=>'Chat',
+                                         'password'=>'123456',
+                                         'id_site' => 1,
+                                         'idabon' => '12345',
+                                         'user_groups' => [$group]]);
+
+    $this->logged_user->beAbonneSIGB()->assertSave();
+    ZendAfi_Auth::getInstance()->logUser($this->logged_user);
+  }
+
+
+  /** @test */
+  public function htmlShouldContainsRessourceNotAvailable() {
+    Class_Album_UsageConstraint::find(2)->setOrderDate('1900-05-02 14:14:14')->assertSave();
+    $this->_html = $this->_helper->renderAlbum($this->book);
+
+    $this->assertXPathContentContains($this->_html,
+                                      '//div//span[@class="error"]',
+                                      'La ressource n\'est plus disponible.');
+  }
+
+
+  /** @test */
+  public function htmlShouldContainsQuotaEmpty() {
+    $this->_http
+      ->whenCalled('open_url')
+      ->answers('');
+
+    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_PER_USER', 3);
+
+    foreach(range(1, 3) as $step)
+      $this->fixture('Class_Loan_Pnb',
+                     ['id' => $step,
+                      'user_id' => $this->logged_user->getId(),
+                      'record_origin_id' => $step,
+                      'expected_return_date' => '2022-06-01']);
+
+    $this->_html = $this->_helper->renderAlbum($this->book);
+
+    $this->assertXPathContentContains($this->_html,
+                                      '//div//span[@class="error"]',
+                                      'Emprunt impossible. Vous avez atteint votre quota de 3 emprunts');
+  }
+
+
+  /** @test */
+  public function htmlShouldContainsNextAvailableLoanDate() {
+    Class_Album_UsageConstraint::find(1)->setMaxNumberOfUsers(0)->assertSave();
+    Class_AdminVar::set('DILICOM_PNB_LOAN_COUNT_LIMIT',0);
+    $this->fixture('Class_Loan_Pnb',
+                   ['id' => 1,
+                    'user_id' => $this->logged_user->getId(),
+                    'record_origin_id' => 1,
+                    'order_line_id' => 'x321',
+                    'expected_return_date' => '2022-06-01 20:10:00']);
+
+    $this->_html = $this->_helper->renderAlbum($this->book);
+
+    $this->assertXPathContentContains($this->_html,
+                                      '//div//span[@class="error"]',
+                                      utf8_encode('Emprunt impossible. Le nombre d\'emprunts simultanés pour ce document est atteint. Le prochain emprunt sera possible le mercredi 01 juin à 20h10'));
+  }
+
+
+  /** @test */
+  public function onUnknownReturnDateHtmlShouldNotContainsNextAvailableLoanDate() {
+    Class_Album_UsageConstraint::find(1)->setMaxNumberOfUsers(0)->assertSave();
+    Class_AdminVar::set('DILICOM_PNB_LOAN_COUNT_LIMIT',0);
+    $this->fixture('Class_Loan_Pnb',
+                   ['id' => 1,
+                    'user_id' => $this->logged_user->getId(),
+                    'record_origin_id' => 1,
+                    'order_line_id' => '???',
+                    'expected_return_date' => '2022-06-01 20:10:00']);
+
+    $this->_html = $this->_helper->renderAlbum($this->book);
+
+    $this->assertNotXPathContentContains($this->_html,
+                                         '//div//span[@class="error"]',
+                                         utf8_encode('Le prochain emprunt sera possible'));
+  }
+}
+
+
+
+
+abstract class PnbDilicomBibNumeriqueControllerTestCase extends AbstractControllerTestCase {
+
+  protected
+    $_http,
+    $_time_source,
+    $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Bib',
+                   ['id' => 1,
+                    'gln' => '2345889']);
+
+    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 0);
+    Class_WebService_BibNumerique_Dilicom_Hub::setPhpCommand($this->mock()->whenCalled('rand')->answers('1930')
+                                                             ->whenCalled('hash')->answers('10'));
+    $logged_user = $this->fixture('Class_Users',
+                                  ['id' => 6,
+                                   'nom'=>'Pito',
+                                   'login'=>'Chat',
+                                   'password'=>'123456',
+                                   'id_site' => 1,
+                                   'idabon' => '12345',
+                                   'user_groups' => [$this->fixture('Class_UserGroup',
+                                                                    ['id' => '20',
+                                                                     'libelle' => 'Multimedia',
+                                                                     'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]])]]);
+    $logged_user->beAbonneSIGB()->assertSave();
+    ZendAfi_Auth::getInstance()->logUser($logged_user);
+
+    $this->book = $this->fixture('Class_Album',
+                                 ['id' => 3,
+                                  'titre' => 'Totem et Thora',
+                                  'id_origine' => 'Dilicom-88817216',
+                                  'external_uri' => 'http://www.edenlivres.fr/p/23416',
+                                  'type_doc_id' => Class_TypeDoc::DILICOM,
+                                  'isbn' => '435465',
+                                  'notice' => $this->fixture('Class_Notice', ['id' => 38]),
+                                  'items' => [$this->fixture('Class_Album_Item',
+                                                             ['id' => 1,
+                                                              'album_id' => 3,
+                                                              'loan_count' => 2,
+                                                              'quantity' => 4,
+                                                              'usage_constraints' => [$this->fixture('Class_Album_UsageConstraint',
+                                                                                                     ['id' => 1,
+                                                                                                      'album_id' => 3,
+                                                                                                      'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+                                                                                                      Class_Album_UsageConstraint::MAX_NB_OF_USERS => 40,
+                                                                                                      Class_Album_Usageconstraint::QUANTITY => 50,
+                                                                                                      Class_Album_UsageConstraint::DURATION => '100']),
+                                                                                      $this->fixture('Class_Album_UsageConstraint',
+                                                                                                     ['id' => 2,
+                                                                                                      'album_id' => 3,
+                                                                                                      'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
+                                                                                                      Class_Album_UsageConstraint::ORDER_LINE_ID => 'x321',
+                                                                                                      Class_Album_UsageConstraint::DURATION => '100',
+                                                                                                      Class_Album_UsageConstraint::ORDER_DATE => '2015-04-01 00:00:00'])
+                                                              ]
+                                                             ]
+                                    )]
+                                 ]);
+
+    RessourcesNumeriquesFixtures::activateDilicom();
+
+
+    $this->_http = Storm_Test_ObjectWrapper::mock();
+    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient($this->_http);
+
+
+    $this->_time_source = new TimeSourceForTest('2014-05-02 14:14:14');
+    Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource($this->_time_source);
+    Class_Album_UsageConstraint::setTimeSource($this->_time_source);
+    Class_Album_UsageConstraints::setTimeSource($this->_time_source);
+    Class_Loan_Pnb::setTimeSource($this->_time_source);
+  }
+
+
+  public function tearDown() {
+    Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource(null);
+    Class_Album_UsageConstraint::setTimeSource(null);
+    Class_Loan_Pnb::setTimeSource(null);
+
+    parent::tearDown();
+  }
+}
+
+
+
+
+abstract class PnbDilicomBibNumeriqueControllerLoanBookActionTestCase extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
+
+    Class_AdminVar::set('DILICOM_PNB_LOAN_COUNT_LIMIT', 0);
+
+    $update_status_url = 'https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0';
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/loanBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&DRMinfo.readerPass=MTA%3D&DRMinfo.readerHint=Votre+login+est+%22drm%22+et+votre+mot+de+passe+est+%220000%22&DRMinfo.readerId=drm&orderLineId=x321&loanId=1&ean13=435465&accessMedium=DOWNLOAD&localization=EX_SITU&loanEndDate=2014-08-10T14%3A14%3A14%2B0200',
+             ['auth' => ['user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookResponse())
+
+      ->whenCalled('open_url')
+      ->with($update_status_url,
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->willDo(function() use ($update_status_url)
+               {
+                 $this->_http
+                   ->whenCalled('open_url')
+                   ->with($update_status_url,
+                          ['auth' => [ 'user' => 'afi-bib',
+                                      'password' => 'secretPassword']])
+                   ->answers(DilicomFixtures::getLoanStatusAfterLoanResponse());
+                 return DilicomFixtures::getLoanStatusResponse();
+               })
+      ->beStrict();
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerMobileTest extends PnbDilicomBibNumeriqueControllerLoanBookActionTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $_SERVER['HTTP_USER_AGENT'] = 'iPhone';
+    Class_Profil::getCurrentProfil()
+      ->beTelephone()
+      ->assertSave();
+  }
+
+
+  public function tearDown() {
+    unset($_SERVER['HTTP_USER_AGENT']);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function loanBookResoponseShouldContainsDownloadUrl() {
+    $this->dispatch('/telephone/bib-numerique/loan-book/id/3', true);
+    $this->assertRedirectTo('https://pnb-dilicom.centprod.com/v3//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerAjaxPopupBookActionTest extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  /** @test */
+  public function consultBookShouldContainsLinkToOpenAjax() {
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/consultBook?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId=x321&accessMedium=STREAMING&localization=IN_SITU&consultEndDate=2014-05-02T15%3A14%3A14%2B0200&ean13=435465&ipAddress=127.0.0.1&glnColl=afi-bib&loanerColl=2345889&loanId=n4y4nq63',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookResponse())
+      ->beStrict();
+
+    $this->dispatch('/bib-numerique/consult-book-ajax/id/3', true);
+    $this->assertXpathContentContains('//div[@class="popup-content"]//button[contains(@onclick, "/bib-numerique/consult-book-open-ajax/id/3")][@data-popup="true"][@class="bouton validate"]', 'Oui');
+
+    $this->assertXpathContentContains('//button[contains(@onclick, "window.location.href")]', 'Non');
+  }
+
+
+  /** @test */
+  public function popupConsultBookOpenShouldContainsLinkToOpenPnbUrl() {
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/consultBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&orderLineId=x321&loanId=n4y4nq63&ean13=435465&accessMedium=STREAMING&localization=IN_SITU&consultEndDate=2014-05-02T15%3A14%3A14%2B0200&ipAddress=127.0.0.1&returnMessage=La+requ%C3%AAte+%22consultBook%22+n%27a+pas+%C3%A9t%C3%A9+trait%C3%A9e+correctement.',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookResponse())
+      ->beStrict();
+
+    $this->dispatch('/bib-numerique/consult-book-open-ajax/id/3', true);
+    $this->assertContains('<div class=\"popup-content\"><a href=\"https:\/\/pnb-dilicom.centprod.com\/v3\/\/link\/3025594195810\/LOAN\/WIKI001\/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do\" target=\"_blank\" class=\"button blue\" onclick=\"opacDialogClose();\">Lire en ligne<\/a><\/div>"',
+                          $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function popupLoanBookOpenAjaxWithPnbErrorShouldContainsScriptToReloadPage() {
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/consultBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&orderLineId=x321&loanId=n4y4nq63&ean13=435465&accessMedium=STREAMING&localization=IN_SITU&consultEndDate=2014-05-02T15%3A14%3A14%2B0200&ipAddress=127.0.0.1&returnMessage=La+requ%C3%AAte+%22consultBook%22+n%27a+pas+%C3%A9t%C3%A9+trait%C3%A9e+correctement.',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookErrorResponse())
+      ->beStrict();
+
+    $this->dispatch('/bib-numerique/consult-book-open-ajax/id/3', true);
+    $this->assertContains('<script>location.href=\"\/recherche\/viewnotice\/id\/3\/render\/false\"<\/script>', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function loanBookPopupShouldContainsQuestion() {
+    $this->dispatch('/bib-numerique/loan-book-ajax/id/3/render/popup', true);
+    $this->assertContains('<p>Êtes vous sûr de vouloir emprunter ce document ?</p>',
+                          json_decode($this->_response->getBody())->content);
+  }
+
+
+  /** @test */
+  public function loanBookPopupShouldContainsLinkToDownload() {
+    $this->dispatch('/bib-numerique/loan-book-ajax/id/3', true);
+    $this->assertXpathContentContains('//button[contains(@onclick, "/bib-numerique/download-loan-book-ajax/id/3")][@data-popup="true"][@class="bouton validate"]', 'Oui');
+  }
+
+
+  /** @test */
+  public function loanBookPopupShouldContainDefaultMessage() {
+    $this->dispatch('/bib-numerique/loan-book-ajax/id/3/render/popup', true);
+    $this->assertContains('Votre compte sera mis à jour dans un délai de 15 minutes après le retour anticipé du document.', json_decode($this->_response->getBody())->content);
+  }
+
+
+  /** @test */
+  public function loanBookPopupShouldContainDefinedMessage() {
+    $this->fixture('Class_AdminVar',
+                   ['id' => 'DILICOM_PNB_LOAN_WARNING_MESSAGE',
+                    'valeur' => 'Don\'t use this !'
+                   ]);
+    $this->dispatch('/bib-numerique/loan-book-ajax/id/3/render/popup', true);
+    $this->assertContains('Don\'t use this !', json_decode($this->_response->getBody())->content);
+  }
+
+
+  /** @test */
+  public function downloadLinkShouldAnswersDilicomLink() {
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/loanBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&DRMinfo.readerPass=MTA%3D&DRMinfo.readerHint=Votre+login+est+%22drm%22+et+votre+mot+de+passe+est+%220000%22&DRMinfo.readerId=drm&orderLineId=x321&loanId=1&ean13=435465&accessMedium=DOWNLOAD&localization=EX_SITU&loanEndDate=2014-08-10T14%3A14%3A14%2B0200',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookResponse())
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse())
+      ->beStrict();
+
+    $this->dispatch('bib-numerique/download-loan-book-ajax/id/3', true);
+    $this->assertContains('"<div class=\"popup-content\"><a href=\"https:\/\/pnb-dilicom.centprod.com\/v3\/\/link\/3025594195810\/LOAN\/WIKI001\/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do\" class=\"button blue\" onclick=\"opacDialogClose();\">T\u00e9l\u00e9charger<\/a><\/div>"', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function downloadLinkWithPnbErrorShouldConstainsScriptToReloadPage() {
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/loanBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&DRMinfo.readerPass=MTA%3D&DRMinfo.readerHint=Votre+login+est+%22drm%22+et+votre+mot+de+passe+est+%220000%22&DRMinfo.readerId=drm&orderLineId=x321&loanId=1&ean13=435465&accessMedium=DOWNLOAD&localization=EX_SITU&loanEndDate=2014-08-10T14%3A14%3A14%2B0200',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookErrorResponse())
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse())
+      ->beStrict();
+
+    $this->dispatch('bib-numerique/download-loan-book-ajax/id/3', true);
+    $this->assertContains('<script>location.href=\"\/recherche\/viewnotice\/id\/3\/render\/false\"<\/script>', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function downloadLinkWithNoConnectedUserShouldRenderLoginPopup() {
+    ZendAfi_Auth::getInstance()->clearIdentity();
+    $this->dispatch('bib-numerique/download-loan-book-ajax/id/3', true);
+    $this->assertContains('"title":"Authentification"', $this->_response->getBody());
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerConsultBookActionTest extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/consultBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&orderLineId=x321&loanId=n4y4nq63&ean13=435465&accessMedium=STREAMING&localization=IN_SITU&consultEndDate=2014-05-02T15%3A14%3A14%2B0200&ipAddress=195.251.88.223&returnMessage=La+requ%C3%AAte+%22consultBook%22+n%27a+pas+%C3%A9t%C3%A9+trait%C3%A9e+correctement.',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers('{"orderLineId":"54e7473f975a2fa6aa4d3e17","consultEndDate":"2014-05-02T15:14:14+0200","loanId":"3","returnStatus":"OK","returnMessage":[],"requestId":"awvzrcttestpnbv2_000000039_201503051511","link":{"url":"https://pnb-test.centprod.com/v3//link/3056000302801/CONSULT/3/9791023501766-FGR9FJJGJCMXYMB8BO87XR9TPHDN9QNS.do","aformatDescription":"EPUB","mimetype":"application/epub+zip","ean13":"9791023501766","format":"E101"}}')
+      ->beStrict();
+
+
+    $_SERVER['HTTP_X_FORWARDED_FOR'] = '195.251.88.223';
+
+    $this->dispatch('/bib-numerique/consult-book/id/3', true);
+  }
+
+
+  public function tearDown() {
+    Class_WebService_BibNumerique_Dilicom_Hub::setDefaultHttpClient(null);
+    RessourcesNumeriquesFixtures::deactivateDilicom();
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function openUrlShouldHaveBeenCalled() {
+    $this->assertTrue($this->_http->methodHasBeenCalled('open_url'));
+  }
+
+
+  /** @test */
+  public function responseShouldRedirectToConsultBookUrl() {
+    $this->assertRedirectTo('https://pnb-test.centprod.com/v3//link/3056000302801/CONSULT/3/9791023501766-FGR9FJJGJCMXYMB8BO87XR9TPHDN9QNS.do');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerConsultBookWithErrorsActionTest extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->answers('{"orderLineId":"54e7473f975a2fa6aa4d3e17","consultEndDate":"","loanId":"3","returnStatus":"ERROR","returnMessage":["Crash boom"],"requestId":"awvzrcttestpnbv2_000000039_201503051511","link":{}}');
+
+    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
+
+    $this->dispatch('/bib-numerique/consult-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function responseShouldRedirectToAlbumRecord38() {
+    $this->assertRedirectTo('/recherche/viewnotice/id/38', $this->getResponseLocation());
+  }
+
+
+  /** @test */
+  public function errorMessageShouldBeInNotifications() {
+    $this->assertFlashMessengerContentContains('Crash boom');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionTest extends PnbDilicomBibNumeriqueControllerLoanBookActionTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function responseShouldReturnDownloadUrl() {
+    $this->assertRedirectTo('https://pnb-dilicom.centprod.com/v3//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do');
+  }
+
+
+  /** @test */
+  public function bokehLoanShouldHaveBeenSaved() {
+    $this->assertNotNull(Class_Loan_Pnb::findFirstBy(['record_origin_id' => 'Dilicom-88817216']));
+  }
+
+
+  /** @test */
+  public function bokehLoanOrderLineIdShouldBe() {
+    $loan = Class_Loan_Pnb::findFirstBy(['record_origin_id' => 'Dilicom-88817216']);
+    $this->assertEquals('x321', $loan->getOrderLineId());
+  }
+
+
+  /** @test */
+  public function loanUrlShouldHaveBeenSaved() {
+    $this->assertEquals('https://pnb-dilicom.centprod.com/v3//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do', Class_Loan_Pnb::findFirstBy(['record_origin_id' => 'Dilicom-88817216'])->getLoanLink());
+  }
+
+
+  /** @test */
+  public function itemQuantityShouldEqualsTwentySeven() {
+    $this->assertEquals(27, $this->book->getItems()[0]->getQuantity());
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionErrorsTest extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
+
+    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 10);
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/loanBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&DRMinfo.readerPass=MTA%3D&DRMinfo.readerHint=Votre+login+est+%22drm%22+et+votre+mot+de+passe+est+%220000%22&DRMinfo.readerId=drm&orderLineId=x321&loanId=1&ean13=435465&accessMedium=DOWNLOAD&localization=EX_SITU&loanEndDate=2014-05-12T14%3A14%3A14%2B0200',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookErrorResponse())
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse())
+      ->beStrict();
+
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function responseShouldRedirectToAlbumNoticeId38() {
+    $this->assertRedirectTo('/recherche/viewnotice/id/38',
+                            $this->getResponseLocation());
+  }
+
+
+  /** @test */
+  public function errorMessageShouldBeInNotifications() {
+    $this->assertFlashMessengerContentContains('Are you trying to download this book ?');
+  }
+
+
+  /** @test */
+  public function loanShouldHaveBeedDeleted() {
+    $this->assertNull(Class_Loan_Pnb::find(1));
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookWrongAlbumTest extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  /** @test */
+  public function responseShouldRedirectToReferer() {
+    $_SERVER['HTTP_REFERER'] = '/recherche/viewnotice/id/3789';
+    $this->dispatch('/bib-numerique/loan-book/id/32', true);
+    $this->assertRedirectTo('/recherche/viewnotice/id/3789');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionTwiceWithSameUserTest extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getEndedLoans?login=afi-bib&password=secretPassword&glnContractor=123456789&loanId%5B0%5D=5',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getEndedLoansResponse())
+
+      ->beStrict();
+
+    $this->fixture('Class_Loan_Pnb',
+                   ['id' => 5,
+                    'user_id' => '6',
+                    'record_origin_id' => 'Dilicom-88817216',
+                    'expected_return_date' => '2014-05-02 18:14:14',
+                    'loan_link' => 'https://pnb-dilicom.centprod.com/v3//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do']);
+
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function responseShouldReturnDownloadUrl() {
+    $this->assertRedirectTo('https://pnb-dilicom.centprod.com/v3//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do');
+  }
+}
+
+
+
+abstract class PnbDilicomBibNumeriqueControllerSecondUserTestCase extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $_SERVER['HTTP_REFERER'] = '/viewnotice/id/3';
+
+    $second_user = $this->fixture('Class_Users',
+                                  ['id' => 78,
+                                   'nom'=>'Dily',
+                                   'login'=>'Chaton',
+                                   'password'=>'123456',
+                                   'id_site' => 1,
+                                   'idabon' => '654321',
+                                   'user_groups' => [Class_UserGroup::find(20)]]);
+
+    $second_user->beAbonneSIGB()->assertSave();
+    ZendAfi_Auth::getInstance()->logUser($second_user);
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->never();
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionWithASecondUserAndLoanCountExcededTest extends PnbDilicomBibNumeriqueControllerSecondUserTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusNoSimultaneousLoanLeftResponse())
+      ->beStrict();
+
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function responseShouldRedirectToAlbumRecord38() {
+    $this->assertRedirectTo('/recherche/viewnotice/id/38');
+  }
+
+
+  /** @test */
+  public function notificationShouldReturnNoLoansAvailable() {
+    $this->assertFlashMessengerContentContains('Emprunt impossible. Le nombre d\'emprunts simultanés pour ce document est atteint.');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionBookUnavailableTest extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  /** @test */
+  public function notificationShouldReturnNoLoansAvailableAfterAvailabilityEndDate() {
+    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 0);
+    $this->_time_source->setTime(strtotime('2015-07-10'));
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse())
+      ->beStrict();
+
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+
+    $this->assertFlashMessengerContentContains('Emprunt impossible. La ressource n\'est plus disponible');
+  }
+
+
+  /** @test */
+  public function notificationShouldReturnNoLoansAvailableWhenLoanDurationLessThanRemainingDays() {
+    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 5);
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse())
+      ->beStrict();
+
+    $this->_time_source->setTime(strtotime('2015-07-6'));
+
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+
+    $this->assertFlashMessengerContentContains('Emprunt impossible. La ressource n\'est plus disponible');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionWithTwoItemsTest extends PnbDilicomBibNumeriqueControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse())
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x458&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse())
+
+      ->beStrict();
+
+    $this->book->addItem($this->fixture('Class_Album_Item',
+                                        ['id' => 2,
+                                         'loan_count' => 0,
+                                         'quantity' => 10,
+                                         'usage_constraints' => [$this->fixture('Class_Album_UsageConstraint',
+                                                                                ['id' => 4,
+                                                                                 'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+                                                                                 Class_Album_UsageConstraint::MAX_NB_OF_USERS => 40,
+                                                                                 Class_Album_Usageconstraint::QUANTITY => 50,
+                                                                                 Class_Album_UsageConstraint::DURATION => 10]),
+                                                                 $this->fixture('Class_Album_UsageConstraint',
+                                                                                ['id' => 5,
+                                                                                 'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
+                                                                                 Class_Album_UsageConstraint::ORDER_LINE_ID => 'x458',
+                                                                                 Class_Album_UsageConstraint::DURATION => '100',
+                                                                                 Class_Album_UsageConstraint::ORDER_DATE => '2015-07-01 00:00:00'])]]));
+  }
+
+
+  protected function _dispatchAndAssertSecondBookLoaned() {
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+    $this->assertRedirectTo('https://pnb-dilicom.centprod.com/v3//link/3025594195810/LOAN/WIKI001/9782021153057-NUMOIY0785CYO0IGCV83DE9DOAOC1Y1O.do',
+                            $this->getResponseLocation());
+  }
+
+
+  /** @test */
+  public function withFirstItemExpiredShouldLoanSecondItem() {
+    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_DURATION', 0);
+    $this->_time_source->setTime(strtotime('2015-07-10'));
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/loanBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&DRMinfo.readerPass=MTA%3D&DRMinfo.readerHint=Votre+login+est+%22drm%22+et+votre+mot+de+passe+est+%220000%22&DRMinfo.readerId=drm&orderLineId=x458&loanId=1&ean13=435465&accessMedium=DOWNLOAD&localization=EX_SITU&loanEndDate=2015-07-20T00%3A00%3A00%2B0200',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookResponse())
+      ->beStrict();
+
+    $this->_dispatchAndAssertSecondBookLoaned();
+  }
+
+
+  /** @test */
+  public function withFirstItemLoandCountExcededShouldLoanSecondItem() {
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x458&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusNoSimultaneousLoanLeftResponse())
+
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/loanBook?login=afi-bib&password=secretPassword&glnContractor=123456789&glnLoaner=2345889&UserInfo.year=1930&UserInfo.gender=H&DRMinfo.readerPass=MTA%3D&DRMinfo.readerHint=Votre+login+est+%22drm%22+et+votre+mot+de+passe+est+%220000%22&DRMinfo.readerId=drm&orderLineId=x321&loanId=1&ean13=435465&accessMedium=DOWNLOAD&localization=EX_SITU&loanEndDate=2014-08-10T14%3A14%3A14%2B0200',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::loanBookResponse())
+      ->beStrict();
+
+    $this->_dispatchAndAssertSecondBookLoaned();
+  }
+
+
+  /** @test */
+  public function withSecondItemExpiredShouldReturnNoLoansAvailableAfterAvailabilityEndDate() {
+    $this->_time_source->setTime(strtotime('2016-07-10'));
+
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+
+    $this->assertFlashMessengerContentContains('Emprunt impossible. La ressource n\'est plus disponible');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionWithASecondUserAndGlobalLoanCountExcededTest extends PnbDilicomBibNumeriqueControllerSecondUserTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_http->whenCalled('open_url')
+                ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+                       ['auth' => [ 'user' => 'afi-bib',
+                                   'password' => 'secretPassword']])
+                ->answers(DilicomFixtures::getLoanStatusResponse());
+
+    Class_AdminVar::set('DILICOM_PNB_LOAN_COUNT_LIMIT', 3);
+    Class_Album_Item::find(1)->setLoanCount(3)->save();
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function responseShouldRedirectToAlbumRecord38() {
+    $this->assertRedirectTo('/recherche/viewnotice/id/38');
+  }
+
+
+  /** @test */
+  public function notificationShouldReturnNoLoansAvailable() {
+    $this->assertFlashMessengerContentContains('Emprunt impossible. Le nombre d\'emprunts simultanés pour ce document est atteint.');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionUserLoanCountExceededTest extends PnbDilicomBibNumeriqueControllerSecondUserTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->_http
+      ->whenCalled('open_url')
+      ->answers('');
+
+
+    Class_AdminVar::set('DILICOM_PNB_MAX_LOAN_PER_USER', 3);
+    foreach(range(1, 3) as $step)
+      $this->fixture('Class_Loan_Pnb',
+                     ['id' => $step,
+                      'user_id' => Class_Users::getIdentity()->getId(),
+                      'record_origin_id' => $step,
+                      'expected_return_date' => '2022-06-01']);
+
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function responseShouldRedirectToAlbumRecord38() {
+    $this->assertRedirectTo('/recherche/viewnotice/id/38');
+  }
+
+
+  /** @test */
+  public function notificationShouldReturnNoLoansAvailable() {
+    $this->assertFlashMessengerContentContains('Emprunt impossible. Vous avez atteint votre quota de 3 emprunts.');
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionWithASecondUserAndQuantityExcededTest extends PnbDilicomBibNumeriqueControllerSecondUserTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => ['user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusNoQuantityLeftResponse())
+      ->beStrict();
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function albumShouldHaveBeenUpdateThroughHubGetLoanStatus() {
+    $this->assertContains('getLoanStatus', $this->_http->getFirstAttributeForLastCallOn('open_url'));
+  }
+
+
+  /** @test */
+  public function responseShouldRedirectToAlbumRecord38() {
+    $this->assertRedirectTo('/recherche/viewnotice/id/38');
+  }
+
+
+  /** @test */
+  public function notificationShouldContainsNoLoansAvailable() {
+    $this->assertFlashMessengerContentContains('Emprunt impossible. Le nombre d\'emprunts disponible est épuisé.', $this->_getFlashMessengerMessages()[0]);
+  }
+}
+
+
+
+
+class PnbDilicomBibNumeriqueControllerLoanBookActionWithASecondUserAndOfferValidityExcededTest extends PnbDilicomBibNumeriqueControllerSecondUserTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->_http
+      ->whenCalled('open_url')
+      ->with('https://pnb-test.centprod.com/v3/pnb-numerique/json/getLoanStatus?login=afi-bib&password=secretPassword&glnContractor=123456789&orderLineId%5B0%5D=x321&returnEndedLoan=0',
+             ['auth' => [ 'user' => 'afi-bib',
+                         'password' => 'secretPassword']])
+      ->answers(DilicomFixtures::getLoanStatusResponse());
+
+    $this->_time_source = new TimeSourceForTest('2018-05-02 14:14:14');
+    Class_WebService_BibNumerique_Dilicom_Hub::setTimeSource($this->_time_source);
+    Class_Album_UsageConstraint::setTimeSource($this->_time_source);
+
+    $this->dispatch('/bib-numerique/loan-book/id/3', true);
+  }
+
+
+  /** @test */
+  public function notificationShouldContainsErrorMessage() {
+    $this->assertFlashMessengerContentContains('Emprunt impossible. L\'emprunt du document n\'est plus disponible.', $this->_getFlashMessengerMessages()[0]);
+  }
+}
+
+
+
+
+class PnbDilicomRechercheControllerViewnoticeWithInspectorGadgetTest extends Admin_AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->fixture('Class_Notice',
+                   ['id' => 2,
+                    'type_doc' => Class_TypeDoc::DILICOM,
+                    'unimarc' => '',
+                    'alpha_titre' => '',
+                    'alpha_auteur' => '']);
+
+    $this->dispatch('/opac/recherche/viewnotice/id/2/inspector_gadget/1', true);
+  }
+
+
+  /** @test */
+  public function buttonNoticeBokehShouldBePresent() {
+    $this->assertXPathContentContains('//button', 'Notice Bokeh');
+  }
+
+
+  /** @test */
+  public function buttonDilicomShouldBePresent() {
+    $this->assertXPathContentContains('//button', 'Dilicom');
+  }
+}
+
+
+
+
+class PnbDilicomAdminAlbumControllerImportTest extends Admin_AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    RessourcesNumeriquesFixtures::activateDilicom();
+    $this->dispatch('/admin/album/dilicom', true);
+  }
+
+
+  /** @test */
+  public function titleShouldBeImportPnbDilicom() {
+    $this->assertXPathContentContains('//h1', 'PNB Dilicom');
+  }
+
+
+  /** @test */
+  public function importOffresPnbDilicomShouldBePresent() {
+    $this->assertXPathContentContains('//h2', 'Import des offres Dilicom/PNB');
+  }
+
+
+  /** @test */
+  public function usedRessourcesShouldBePresent() {
+    $this->assertXPathContentContains('//h2', 'Utilisation des ressources PNB Dilicom');
+  }
+
+
+  /** @test */
+  public function formImportOffersShouldContainsFileInputForXML() {
+    $this->assertXPath('//form[contains(@action, "admin/album/dilicom")]//input[@type="file"][@name="offers"]');
+  }
+
+
+  /** @test */
+  public function formShouldHaveSubmitButtonImportXML() {
+    $this->assertXPath('//input[@type="submit"][@value="Importer le fichier XML"]');
+  }
+}
+
+
+
+
+abstract class PnbDilicomAdminAlbumControllerTestCase extends Admin_AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+
+    RessourcesNumeriquesFixtures::activateDilicom();
+
+    $this->fixture('Class_Album',
+                   ['id' => 23,
+                    'titre' => 'Being human being',
+                    'type_doc_id' => Class_TypeDoc::DILICOM]);
+
+    $this->fixture('Class_Album_Item',
+                   ['id' => 1,
+                    'quantity' => '10',
+                    'loan_count' => '2',
+                    'album_id' => 23,
+                    'usage_constraints' =>
+                    [
+                     $this->fixture('Class_Album_UsageConstraint',
+                                    ['id' => 1,
+                                     'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 45,
+                                                                        Class_Album_UsageConstraint::QUANTITY => 30,
+                                                                        Class_Album_UsageConstraint::MAX_NB_OF_USERS => 15])]),
+
+                     $this->fixture('Class_Album_UsageConstraint',
+                                    ['id' => 2,
+                                     'usage_type' => Class_Album_UsageConstraint::DEVICE_SHARE_CONSTRAINT,
+                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::QUANTITY => 6])]),
+
+
+                     $this->fixture('Class_Album_UsageConstraint',
+                                    ['id' => 3,
+                                     'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
+                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 2345,
+                                                                        Class_Album_UsageConstraint::ORDER_LINE_ID => '12385',
+                                                                        Class_Album_UsageConstraint::ORDER_DATE => '2015-03-30'])])
+                    ]
+                   ]);
+
+    $this->fixture('Class_Album_Item',
+                   ['id' => 2,
+                    'quantity' => '20',
+                    'loan_count' => '5',
+                    'album_id' => 23,
+                    'usage_constraints' =>
+                    [
+                     $this->fixture('Class_Album_UsageConstraint',
+                                    ['id' => 1,
+                                     'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 20,
+                                                                        Class_Album_UsageConstraint::QUANTITY => 20,
+                                                                        Class_Album_UsageConstraint::MAX_NB_OF_USERS => 15])]),
+                     $this->fixture('Class_Album_UsageConstraint',
+                                    ['id' => 5,
+                                     'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
+                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 20000,
+                                                                        Class_Album_UsageConstraint::ORDER_LINE_ID => '8765',
+                                                                        Class_Album_UsageConstraint::ORDER_DATE => '2015-08-29'])])
+                    ]
+                   ]);
+
+
+    $this->fixture('Class_CodifGenre',
+                   ['id' => 23,
+                    'libelle' => 'Heavy Metal']);
+
+    $this->fixture('Class_CodifSection',
+                   ['id' => 33,
+                    'libelle' => 'Espace métal']);
+
+    $fondu = $this->fixture('Class_AlbumCategorie',
+                            ['id' => 1301,
+                             'libelle' => 'Fondu']);
+
+    $this->fixture('Class_Album',
+                   ['id' => 9999,
+                    'id_origine' => 'Dilicom-3663608260879',
+                    'titre' => 'Hell is from here to eternity',
+                    'type_doc_id' => Class_TypeDoc::DILICOM,
+                    'genre' => '23',
+                    'sections' => '33',
+                    'categorie' => $fondu])
+         ->addAuthor('Iron Maiden')
+         ->addEditor('EMI')
+         ->addCollection('Temple Of Rock')
+         ->setAnnee(1992)
+      ;
+
+    $this->fixture('Class_Album_Item',
+                   ['id' => 9,
+                    'quantity' => 990000,
+                    'loan_count' => 3,
+                    'album_id' => 9999,
+                    'usage_constraints' =>
+                    [
+                     $this->fixture('Class_Album_UsageConstraint',
+                                    ['id' => 1,
+                                     'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 359,
+                                                                        Class_Album_UsageConstraint::QUANTITY => 999999,
+                                                                        Class_Album_UsageConstraint::MAX_NB_OF_USERS => 1])]),
+
+                     $this->fixture('Class_Album_UsageConstraint',
+                                    ['id' => 2,
+                                     'usage_type' => Class_Album_UsageConstraint::DEVICE_SHARE_CONSTRAINT,
+                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::QUANTITY => 6])]),
+
+
+                     $this->fixture('Class_Album_UsageConstraint',
+                                    ['id' => 3,
+                                     'usage_type' => Class_Album_UsageConstraint::AVAILABILITY_CONSTRAINT,
+                                     'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 999999,
+                                                                        Class_Album_UsageConstraint::ORDER_LINE_ID => '584837a045ce56ef0a072a8b',
+                                                                        Class_Album_UsageConstraint::ORDER_DATE => '2015-03-30 08:08:34'])])
+                    ]
+                   ]);
+
+    $this->fixture('Class_Loan_Pnb',
+                   ['id' => 2,
+                    'record_origin_id' => 'Dilicom-3663608260879',
+                    'subscriber_id' => '000005',
+                    'user_id' => 4077,
+                    'expected_return_date' => '2017-01-20 13:57:33',
+                    'loan_date' => '2016-12-16 13:57:33',
+                    'loan_link' => 'https://pnb-dilicom.centprod.com/v2//XXXXXXXX.do',
+                    'order_line_id' => '584837a045ce56ef0a072a8b',]);
+
+    $this->fixture('Class_Loan_Pnb',
+                   ['id' => 3,
+                    'record_origin_id' => 'Dilicom-3663608260879',
+                    'subscriber_id' => '000006',
+                    'user_id' => 4078,
+                    'expected_return_date' => '2017-12-13 13:57:33',
+                    'loan_date' => '2017-11-13 13:57:33',
+                    'loan_link' => 'https://pnb-dilicom.centprod.com/v2//XXXXXXXX.do',
+                    'order_line_id' => '584837a045ce56ef0a072a8b',]);
+
+    Class_Loan_Pnb::setTimeSource(new TimeSourceForTest('2017-11-14 11:35:05'));
+
+    $this->onLoaderOfModel('Class_Loan_Pnb')
+         ->whenCalled('countByOngoingOrderLineId')->with('584837a045ce56ef0a072a8b')
+         ->answers(1);
+  }
+
+
+  public function tearDown() {
+    RessourcesNumeriquesFixtures::deactivateDilicom();
+    Class_Loan_Pnb::setTimeSource(null);
+
+    parent::tearDown();
+  }
+}
+
+
+
+
+class PnbDilicomAdminAlbumControllerImportDilicomTest extends PnbDilicomAdminAlbumControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/album/dilicom', true);
+  }
+
+
+  /** @test */
+  public function tableSorterPagerJsShouldBeLoaded() {
+    $this->assertXPath('//script[contains(@src, "tablesorter/addons/pager/jquery.tablesorter.pager.min")]');
+  }
+
+
+  /** @test */
+  public function tableSorterPagerCSSShouldBeLoaded() {
+    $this->assertXPath('//link[contains(@href, "/public/opac/js/tablesorter/addons/pager/jquery.tablesorter.pager.css")]');
+  }
+
+
+  /** @test */
+  public function tableSorterPagerShouldBeActivated() {
+    $this->assertXPathContentContains('//script',
+                                      'tablesorterPager({container:');
+  }
+
+
+  /** @test */
+  public function beingHumanTitleShouldBeInTalbe() {
+    $this->assertXPathContentContains('//table//tr/td', 'Being human being');
+  }
+
+
+  /** @test */
+  public function beingHumanActionShouldLinkToEditAlbum() {
+    $this->assertXPath('//table//tr/td/a[contains(@href, "/edit_album/id/23")]', $this->_response->getBody());
+  }
+
+
+  /** @test */
+  public function nbOfLoansShouldBe30() {
+    $this->assertXPathContentContains('//table//tr/td', '10 / 30');
+  }
+
+
+  /** @test */
+  public function nbOfLiveLoansShouldBe2() {
+    $this->assertXPathContentContains('//table//tr/td', '2 / 15');
+  }
+
+
+  /** @test */
+  public function nbOfRemainingDaysInLicenseShouldBe45() {
+    $this->assertXPathContentContains('//table//tr/td', '45');
+  }
+
+
+  /** @test */
+  public function orderDateShouldContains29_08_2015() {
+    $this->assertXPathContentContains('//table//tr/td', '29/08/2015');
+  }
+
+
+  /** @test */
+  public function exportCSVButtonShouldBePresent() {
+    $this->assertXPathContentContains('//div[@class="modules"]/button[contains(@data-url, "/admin/album/dilicom-export-csv")]', 'Exporter le tableau en CSV');
+  }
+
+
+  /** @test */
+  public function exportCSVLoansButtonShouldBePresent() {
+    $this->assertXPathContentContains('//div[@class="modules"]/button[contains(@data-url, "/admin/album/dilicom-export-loans-csv")]', 'Exporter l\'historique des prêts en CSV');
+  }
+
+
+  /** @test */
+  public function nbOfEternityLoansShouldBeTwoOnInfinite() {
+    $this->assertXPathContentContains('//table//tr[3]/td[2]', '2 / ∞');
+  }
+
+
+  /** @test */
+  public function nbOfEternityLiveLoansShouldBe1OnInfinite() {
+    $this->assertXPathContentContains('//table//tr[3]/td[3]', '1 / ∞');
+  }
+
+
+  /** @test */
+  public function durationOfEternityShouldBe359() {
+    $this->assertXPathContentContains('//table//tr[3]/td[4]', '359');
+  }
+
+
+  /** @test */
+  public function licenceExpirationOfEternityShouldBeInfinite() {
+    $this->assertXPathContentContains('//table//tr[3]/td[5]', '∞');
+  }
+}
+
+
+
+
+class PnbDilicomAdminAlbumControllerEditTest extends PnbDilicomAdminAlbumControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->dispatch('/admin/album/editalbum/id/23', true);
+  }
+
+
+  /** @test */
+  public function inputTitreShouldContainsBeingHumanBeing() {
+    $this->assertXPath('//input[@name="titre"][@value="Being human being"]');
+  }
+
+
+  /** @test */
+  public function fieldsetUsageShouldBeVisible() {
+    $this->assertXPathContentContains('//fieldset/legend','Utilisation');
+  }
+
+
+  /** @test */
+  public function dtInUsageShouldContainsPret() {
+    $this->assertXPathContentContains('//fieldset//dl//dt', 'Prêt');
+  }
+
+
+  /** @test */
+  public function dddldtShouldContainsDuration() {
+    $this->assertXPathContentContains('//fieldset//dl/dd/dl/dt', 'Durée (j)');
+  }
+
+
+  /** @test */
+  public function dddlddShouldContains45() {
+    $this->assertXPathContentContains('//fieldset//dl/dd/dl/dd', '45');
+  }
+
+
+  /** @test */
+  public function dddldtShouldContainsDateDeCommande() {
+    $this->assertXPathContentContains('//fieldset//dl/dd/dl/dt', 'Date de commande');
+  }
+
+
+  /** @test */
+  public function dddldtShouldContainsNumeroCommande() {
+    $this->assertXPathContentContains('//fieldset//dl/dd/dl/dt', 'Numéro de commande');
+  }
+
+
+  /** @test */
+  public function dtInUsageShouldContainsAuthorizedDevices() {
+    $this->assertXPathContentContains('//fieldset//dl//dt', 'Appareils autorisés');
+  }
+}
+
+
+
+
+class PnbDilicomAdminAlbumControllerEditPostTest extends PnbDilicomAdminAlbumControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    $this->postDispatch('/admin/album/edit_album/id/23',
+                        ['titre' => 'Tootem']);
+    Class_Album::clearCache();
+    Class_Album_Item::clearCache();
+    Class_Album_UsageConstraint::clearCache();
+  }
+
+
+  /** @test */
+  public function constraintsShouldNotHaveBeenDeleted() {
+    $item = Class_Album::find(23)->getItems()[0];
+    $this->assertNotNull($item->getUsageConstraints()->getLoanConstraint());
+  }
+}
+
+
+
+
+class PnbDilicomAdminAlbumControllerImportDilicomPaginatorTest extends PnbDilicomAdminAlbumControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+    $this->generateDilicomAlbums();
+    $this->dispatch('admin/album/dilicom', true);
+  }
+
+
+  /** @test */
+  public function contextShouldExpectation() {
+    $this->assertXPathContentContains('//div[@class="pager model_table_pager"]', '1', $this->_response->getBody());
+  }
+
+
+  protected function generateDilicomAlbums() {
+    for($id=50; $id<=75; $id++) {
+      $this->fixture('Class_Album',
+                     ['id' => $id,
+                      'titre' => 'Being human n°' . $id,
+                      'type_doc_id' => Class_TypeDoc::DILICOM,
+                      'usage_constraints' =>
+                      [
+                       $this->fixture('Class_Album_UsageConstraint',
+                                      ['id' => 1000 + $id,
+                                       'usage_type' => Class_Album_UsageConstraint::LOAN_CONSTRAINT,
+                                       'serialized_datas' => json_encode([Class_Album_UsageConstraint::DURATION => 45,
+                                                                          Class_Album_UsageConstraint::QUANTITY => 30,
+                                                                          Class_Album_UsageConstraint::MAX_NB_OF_USERS => 15])]),
+
+                       $this->fixture('Class_Album_UsageConstraint',
+                                      ['id' => 100 + $id,
+                                       'usage_type' => Class_Album_UsageConstraint::DEVICE_SHARE_CONSTRAINT,
+                                       'serialized_datas' => json_encode([Class_Album_UsageConstraint::QUANTITY => 6])])
+                      ]
+                     ]);
+    }
+  }
+}
+
+
+
+
+class PnbDilicomAdminAlbumControllerExportCsvTest extends PnbDilicomAdminAlbumControllerTestCase {
+
+  public function setUp() {
+    parent::setUp();
+
+    Class_Album_UsageConstraints::setTimeSource(new TimeSourceForTest('2015-12-04 14:14:14'));
+    $this->dispatch('admin/album/dilicom-export-csv', true);
+  }
+
+
+  public function tearDown() {
+    Class_Album_UsageConstraints::setTimeSource(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function filenameShouldBeDilicomCsv() {
+    $this->assertContains(['name' => 'Content-Type',
+                           'value' => 'text/csv; name="dilicom_csv.csv"',
+                           'replace' => true], $this->_response->getHeaders());
+  }
+
+
+  /** @test */
+  public function csvShouldContainsAlbumsItems() {
+    $this->assertEquals('Titre;"Prêts / Droits";"Nombre de prêts";"Prêts simultanés / Droits";"Prêts simultanés";"Durée de prêt en jours";"Nombre de jours restant sur la licence";"Date de commande";Auteur;Éditeur;Collection;Année;Genre;Section;Catégorie
+"Being human being";"10 / 30";10;"2 / 15";2;45;2095;30/03/2015;;;;;;;"Albums non classés"
+"Being human being";"20 / 20";20;"5 / 15";5;20;19902;29/08/2015;;;;;;;"Albums non classés"
+"Hell is from here to eternity";"2 / ∞";2;"1 / ∞";1;359;∞;30/03/2015;"Iron Maiden";EMI;"Temple Of Rock";1992;"Heavy Metal";"Espace métal";Fondu
+', $this->_response->getBody());
+  }
+}
+
+
+
+
+class PnbDilicomAdminAlbumControllerExportLoansCsvTest extends PnbDilicomAdminAlbumControllerTestCase {
+  public function setUp() {
+    parent::setUp();
+
+    Class_Album_UsageConstraints::setTimeSource(new TimeSourceForTest('2015-12-04 14:14:14'));
+    $this->dispatch('admin/album/dilicom-export-loans-csv', true);
+  }
+
+
+  public function tearDown() {
+    Class_Album_UsageConstraints::setTimeSource(null);
+    parent::tearDown();
+  }
+
+
+  /** @test */
+  public function filenameShouldBeDilicomLoansCsv() {
+    $this->assertContains(['name' => 'Content-Type',
+                           'value' => 'text/csv; name="dilicom_loans_csv.csv"',
+                           'replace' => true],
+                          $this->_response->getHeaders());
+  }
+
+
+  /** @test */
+  public function csvShouldContainsAlbumsItems() {
+    $this->assertEquals('Date;Titre;"Date de commande";Auteur;Éditeur;Collection;Année;Genre;Section;Catégorie
+16/12/2016;"Hell is from here to eternity";30/03/2015;"Iron Maiden";EMI;"Temple Of Rock";1992;"Heavy Metal";"Espace métal";Fondu
+13/11/2017;"Hell is from here to eternity";30/03/2015;"Iron Maiden";EMI;"Temple Of Rock";1992;"Heavy Metal";"Espace métal";Fondu
+',
+                        $this->_response->getBody());
+  }
+}
+
+
+
+
+class PnbDilicomAdminIndexControllerTest extends AbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  /** @test */
+  public function pnbDilicomShouldBePresent() {
+    new ZendAfi_View_Helper_Admin_ContentNav();
+    ZendAfi_View_Helper_Admin_MenuGaucheAdminItem::setAcl(null);
+
+    $super_admin = $this->fixture('Class_Users',
+                                  ['id' => 5,
+                                   'login' => 'super admin',
+                                   'password' => 'pwd',
+                                   'role_level' => ZendAfi_Acl_AdminControllerRoles::SUPER_ADMIN]);
+    ZendAfi_Auth::getInstance()->logUser($super_admin);
+
+    Class_AdminVar::set('BIBNUM', '1');
+    Class_AdminVar::set('DILICOM_PNB', 1);
+    Class_AdminVar::set('DILICOM_PNB_GLN_COLLECTIVITE', 'afi-bib');
+    Class_AdminVar::set('DILICOM_PNB_PWD_COLLECTIVITE', 'secretPassword');
+    Class_AdminVar::set('DILICOM_PNB_SERVER_URL', 'https://pnb-test.centprod.com');
+    Class_AdminVar::set('DILICOM_PNB_GLN_CONTRACTOR', 123456789);
+    Class_AdminVar::set('DILICOM_PNB_IP_ADRESSES', '127.0.0.1');
+
+    $this->dispatch('/admin/index/index', true);
+    $this->assertXPathContentContains('//a', 'PNB Dilicom');
+  }
+}
+
+
+
+
+class PnbDilicomTelephoneRechercheControllerViewNoticeTest extends TelephoneAbstractControllerTestCase {
+
+  protected $_storm_default_to_volatile = true;
+
+
+  public function setUp() {
+    parent::setUp();
+    RessourcesNumeriquesFixtures::activateDilicom();
+    $book = (new DilicomFixtures())->albumTotemThora()
+                                   ->setVisible(true)
+                                   ->setStatus(Class_Album::STATUS_VALIDATED);
+    $book->assertSave();
+    $book->index();
+
+    $this->fixture('Class_Bib',
+                   ['id' => 1,
+                    'libelle' => 'annecy',
+                    'gln' => '2222'
+                   ]);
+
+    $logged_user = $this->fixture('Class_Users',
+                                  ['id' => 6,
+                                   'nom'=>'Pito',
+                                   'login'=>'Chat',
+                                   'password'=>'123456',
+                                   'id_site' => 1,
+                                   'idabon' => '12345',
+                                   'user_groups' => [$this->fixture('Class_UserGroup',
+                                                                    ['id' => '20',
+                                                                     'libelle' => 'Multimedia',
+                                                                     'rights' => [Class_UserGroup::RIGHT_ACCES_PNB_DILICOM]])]]);
+
+    $logged_user->beAbonneSIGB()->assertSave();
+    ZendAfi_Auth::getInstance()->logUser($logged_user);
+  }
+
+
+  /** @test */
+  public function pageShouldContainsLinkToRessouresNumeriques() {
+    $this->dispatch('/telephone/recherche/viewnotice/id/1', true);
+    $this->assertXPathContentContains('//ul[contains(@class, "doctype_112")]//a',
+                                      'Accéder à la ressource');
+  }
+
+
+  /** @test */
+  public function loanBookActionShouldBeLoanBookNotAjax() {
+    $this->dispatch('/telephone/recherche/ressourcesnumeriques/id/1', true);
+    $this->assertXPath('//a[contains(@href, "bib-numerique/loan-book/id/3")]');
+  }
+
+
+  /** @test */
+  public function consultBookActionShouldNotBeVisible() {
+    $this->dispatch('/telephone/recherche/ressourcesnumeriques/id/1', true);
+    $this->assertNotXPath('//a[contains(@href, "bib-numerique/consult-book/id/3")]');
+  }
+
+
+  /** @test */
+  public function previewIframeShouldNotBeDisplayed() {
+    $this->dispatch('/telephone/recherche/ressourcesnumeriques/id/1', true);
+    $this->assertNotXPath('//iframe');
+  }
+}
\ No newline at end of file