From 8298e5585c22174c7e0a9ea8c71696c48fb2af5c Mon Sep 17 00:00:00 2001
From: Tristan Cavelier <tristan.cavelier@tiolive.com>
Date: Wed, 21 Aug 2013 15:48:35 +0200
Subject: [PATCH] jio tests replaced by new one

---
 test/jio/tests.js | 1164 +++++++++++++++++++++------------------------
 1 file changed, 548 insertions(+), 616 deletions(-)

diff --git a/test/jio/tests.js b/test/jio/tests.js
index db3c205..ff308f3 100644
--- a/test/jio/tests.js
+++ b/test/jio/tests.js
@@ -1,537 +1,408 @@
 /*jslint indent: 2, maxlen: 80, nomen: true */
-/*global define, jIO, localStorage, window, test, ok, deepEqual, sinon,
-  expect */
+/*global define, window, exports, jIO, ok, module, test, expect, deepEqual,
+  sinon, FileReader, Blob, setTimeout */
 
-// define([module_name], [dependencies], module);
 (function (dependencies, module) {
   "use strict";
   if (typeof define === 'function' && define.amd) {
     return define(dependencies, module);
   }
   if (typeof exports === 'object') {
-    return module(exports, require('jio'));
+    return module(exports, jIO);
   }
   window.jio_tests = {};
   module(window.jio_tests, jIO);
 }(['exports', 'jio', 'sinon_qunit'], function (exports, jIO) {
   "use strict";
-  var tmp;
+  var JIO = jIO.JIO, fakestorage = {};
 
-  // localStorage cleanup
-  for (tmp in localStorage) {
-    if (localStorage.hasOwnProperty(tmp)) {
-      if (/^jio\//.test(tmp)) {
-        localStorage.removeItem(tmp);
-      }
+  //////////////////////////////////////////////////////////////////////////////
+  // Fake Storage
+
+  function FakeStorage(spec) {
+    this._id = spec.id;
+    if (typeof this._id !== 'string' || this._id.length <= 0) {
+      throw new TypeError(
+        "Initialization error: wrong id"
+      );
     }
   }
 
-  //////////////////////////////////////////////////////////////////////////////
-  // Tools
-
-  function spyJioCallback(result_type, value, message) {
-    return function (err, response) {
-      var val;
-      switch (result_type) {
-      case 'value':
-        val = err || response;
-        break;
-      case 'status':
-        val = (err || {}).status;
-        break;
-      case 'jobstatus':
-        val = (err ? 'fail' : 'done');
-        break;
-      default:
-        ok(false, "Unknown case " + result_type);
-        break;
+  FakeStorage.createNamespace = function (
+    that,
+    method,
+    command,
+    param,
+    options
+  ) {
+    fakestorage[that._id + '/' + method] = {
+      param: param,
+      options: options,
+      success: function () {
+        command.success.apply(command, arguments);
+        delete fakestorage[that._id + '/' + method];
+      },
+      error: function () {
+        command.error.apply(command, arguments);
+        delete fakestorage[that._id + '/' + method];
+      },
+      retry: function () {
+        command.retry.apply(command, arguments);
+        delete fakestorage[that._id + '/' + method];
+      },
+      notify: function () {
+        command.notify.apply(command, arguments);
+      },
+      storage: function () {
+        command.storage.apply(command, arguments);
+      },
+      end: function () {
+        command.end.apply(command, arguments);
+      },
+      commit: function () {
+        command.commit.apply(command, arguments);
+      },
+      free: function () {
+        delete fakestorage[that._id + '/' + method];
       }
-      deepEqual(val, value, message);
     };
-  }
-  exports.spyJioCallback = spyJioCallback;
-
-  // XXX docstring
-  function isUuid(uuid) {
-    var x = "[0-9a-fA-F]";
-    if (typeof uuid !== "string") {
-      return false;
-    }
-    return (uuid.match(
-      "^" + x + "{8}-" + x + "{4}-" +
-        x + "{4}-" + x + "{4}-" + x + "{12}$"
-    ) === null ? false : true);
-  }
-  exports.isUuid = isUuid;
-
-  // XXX docstring
-  exports.jsonlocalstorage = {
-    clear: function () {
-      return localStorage.clear();
-    },
-    getItem: function (item) {
-      var value = localStorage.getItem(item);
-      return value === null ? null : JSON.parse(value);
-    },
-    setItem: function (item, value) {
-      return localStorage.setItem(item, JSON.stringify(value));
-    },
-    removeItem: function (item) {
-      return localStorage.removeItem(item);
-    }
   };
 
-  function objectifyDocumentArray(array) {
-    var obj = {}, k;
-    for (k = 0; k < array.length; k += 1) {
-      obj[array[k]._id] = array[k];
-    }
-    return obj;
-  }
-
-  function closeAndcleanUpJio(jio) {
-    jio.close();
-    exports.jsonlocalstorage.removeItem("jio/id/" + jio.getId());
-    exports.jsonlocalstorage.removeItem("jio/job_array/" + jio.getId());
-  }
-  exports.closeAndcleanUpJio = closeAndcleanUpJio;
+  FakeStorage.makeMethod = function (method) {
+    return function (command, param, options) {
+      FakeStorage.createNamespace(this, method, command, param, options);
+    };
+  };
 
-  function getJioLastJob(jio) {
-    return (exports.jsonlocalstorage.getItem(
-      "jio/job_array/" + jio.getId()
-    ) || [undefined]).pop();
-  }
+  FakeStorage.prototype.post = FakeStorage.makeMethod('post');
+  FakeStorage.prototype.put = FakeStorage.makeMethod('put');
+  FakeStorage.prototype.get = FakeStorage.makeMethod('get');
+  FakeStorage.prototype.remove = FakeStorage.makeMethod('remove');
+  FakeStorage.prototype.putAttachment = FakeStorage.makeMethod('putAttachment');
+  FakeStorage.prototype.getAttachment = FakeStorage.makeMethod('getAttachment');
+  FakeStorage.prototype.removeAttachment =
+    FakeStorage.makeMethod('removeAttachment');
+  FakeStorage.prototype.check = FakeStorage.makeMethod('check');
+  FakeStorage.prototype.repair = FakeStorage.makeMethod('repair');
+  FakeStorage.prototype.allDocs = FakeStorage.makeMethod('allDocs');
+
+  jIO.addStorage('fake', FakeStorage);
+  exports.fakestorage = fakestorage;
 
   //////////////////////////////////////////////////////////////////////////////
-  // Compatibility
-
-  function ospy(o, result_type, value, message, function_name) {
-    function_name = function_name || 'f';
-    o[function_name] = function (err, response) {
-      var val;
-      switch (result_type) {
-      case 'value':
-        val = err || response;
-        break;
-      case 'status':
-        val = (err || {}).status;
-        break;
-      case 'jobstatus':
-        val = (err ? 'fail' : 'done');
-        break;
-      default:
-        ok(false, "Unknown case " + result_type);
-        break;
-      }
-      deepEqual(val, value, message);
-    };
-    sinon.spy(o, function_name);
-  }
-  exports.ospy = ospy;
+  // Tests
 
-  function otick(o, a, b) {
-    var tick = 10000, function_name = 'f';
-    if (typeof a === 'number' && !isNaN(a)) {
-      tick = a;
-      a = b;
-    }
-    if (typeof a === 'string') {
-      function_name = a;
-    }
-    o.clock.tick(tick);
-    if (!o[function_name].calledOnce) {
-      if (o[function_name].called) {
-        ok(false, 'too much results');
-      } else {
-        ok(false, 'no response');
-      }
-    }
-  }
-  exports.otick = otick;
+  module('JIO');
 
-  //////////////////////////////////////////////////////////////////////////////
-  // Dummy Storage
+  /**
+   * Tests the instance initialization
+   */
+  test('Init', function () {
+    expect(1);
+    var workspace = {}, jio = new JIO(undefined, {
+      "workspace": workspace
+    });
 
-  // XX docstring
-  function dummyStorage(spec, my) {
-    var that = my.basicStorage(spec, my);
+    // tests if jio is an object
+    ok(typeof jio === 'object', 'Init ok!');
+  });
 
-    that._mode = spec.mode || 'normal';
-    that._key = spec.key;
-    that._value = spec.value;
+  /**
+   * Tests a wrong command
+   */
+  test('Wrong parameters', function () {
+    expect(2);
+    var result, jio = new JIO({
+      "type": "fake",
+      "id": "Wrong para"
+    }, {
+      "workspace": {}
+    });
 
-    if (that._mode === 'spec error') {
-      throw new TypeError(
-        "Initialization error set by the storage description."
-      );
+    try {
+      jio.post(); // post(kwargs, [options], [callbacks]);
+      result = "No error thrown";
+    } catch (e1) {
+      result = e1.name + ": " + e1.message;
     }
+    deepEqual(
+      result,
+      "TypeError: JIO().post(): Argument 1 is not of type 'object'",
+      "Wrong parameter"
+    );
 
-    that.specToStore = function () {
-      return {
-        "mode": that._mode,
-        "key": that._key,
-        "value": that._value
-      };
-    };
+    try {
+      jio.allDocs(); // allDocs([options], [callbacks]);
+      result = "No error thrown";
+    } catch (e2) {
+      result = e2.name + ": " + e2.message;
+    }
+    deepEqual(result, "No error thrown", "Good parameter");
+  });
 
-    that.post = function (command) {
-      setTimeout(function () {
-        var metadata = command.cloneDoc(), options = command.cloneOption();
-        if (that._mode === 'no response') {
-          return;
-        }
-        // if (that._mode === 'no response + timeout reset') {
-        //   return setTimeout(function () {
-        //     command.resetTimeout();
-        //   }, that._value);
-        // }
-        if (that._mode === 'invalid error response') {
-          return that.error();
-        }
-        if (that._mode === 'always fail') {
-          return that.error({
-            "error": "conflict",
-            "message": "",
-            "reason": "unknown",
-            "status": 409,
-            "statusText": "Conflict"
-          });
-        }
-        if (that._mode === 'post response no id') {
-          return that.success();
-        }
-        that.success({"id": "document id a", "ok": true});
-      });
-    };
+  /**
+   * Tests a storage initialization error
+   */
+  test('Description Error', function () {
+    var clock, jio;
+    expect(2);
+    clock = sinon.useFakeTimers();
+    jio = new JIO({
+      "type": "blue"
+    }, {
+      "workspace": {}
+    });
 
-    that.put = function (command) {
-      setTimeout(function () {
-        var metadata = command.cloneDoc(), options = command.cloneOption();
-        if (that._mode === 'retry') {
-          if (!dummyStorage[that._key]) {
-            dummyStorage[that._key] = 0;
-          }
-          dummyStorage[that._key] += 1;
-          if (dummyStorage[that._key] === that._value) {
-            return that.success({"id": metadata._id, "ok": true});
-          }
-          return that.retry();
-        }
-        that.success({"id": metadata._id, "ok": true});
-      });
-    };
+    // Tests wrong storage type
+    jio.post({}).always(function (answer) {
+      deepEqual(answer, {
+        "error": "internal_storage_error",
+        "message": "Check if the storage description respects the " +
+          "constraints provided by the storage designer. (TypeError: " +
+          "Unknown storage 'blue')",
+        "reason": "invalid description",
+        "status": 551,
+        "statusText": "Internal Storage Error"
+      }, "Unknown storage");
+    });
+    clock.tick(1);
+
+    // Tests wrong storage description
+    jio = new JIO({
+      "type": "fake",
+      "id": ""
+    }, {
+      "workspace": {}
+    });
 
-    that.remove = function (command) {
-      setTimeout(function () {
-        var metadata = command.cloneDoc(), options = command.cloneOption();
-        that.success({"id": metadata._id, "ok": true});
-      });
-    };
+    jio.post({}).always(function (answer) {
+      deepEqual(answer, {
+        "error": "internal_storage_error",
+        "message": "Check if the storage description respects the " +
+          "constraints provided by the storage designer. (TypeError: " +
+          "Initialization error: wrong id)",
+        "reason": "invalid description",
+        "status": 551,
+        "statusText": "Internal Storage Error"
+      }, "Initialization error");
+    });
+    clock.tick(1);
+  });
 
-    return that;
-  }
+  /**
+   * Tests a command which does not respond
+   */
+  test('No Response or Response Timeout', function () {
+    var clock, jio, state;
+    expect(5);
+    clock = sinon.useFakeTimers();
+    jio = new JIO({
+      "type": "fake",
+      "id": "1 No Respons"
+    }, {
+      "workspace": {}
+    });
 
-  jIO.addStorageType('dummy', dummyStorage);
-  exports.dummyStorage = dummyStorage;
+    // tests default timeout
+    jio.post({}).always(function (answer) {
+      deepEqual(answer, {
+        "error": "request_timeout",
+        "message": "Operation canceled after around " +
+          "10000 milliseconds of inactivity.",
+        "reason": "timeout",
+        "status": 408,
+        "statusText": "Request Timeout"
+      }, "Timeout error (default timeout)");
+    });
+    clock.tick(1);
+    clock.tick(10000); // wait 10 seconds
+    fakestorage['1 No Respons/post'].free();
+
+    jio = new JIO({
+      "type": "fake",
+      "id": "2 No Respons"
+    }, {
+      "workspace": {}
+    });
 
-  //////////////////////////////////////////////////////////////////////////////
-  // Tests
+    // tests storage timeout
+    state = "Not called yet";
+    jio.post({}).always(function (answer) {
+      state = "Called";
+      deepEqual(answer, {
+        "error": "request_timeout",
+        "message": "Operation canceled after around " +
+          "10000 milliseconds of inactivity.",
+        "reason": "timeout",
+        "status": 408,
+        "statusText": "Request Timeout"
+      }, "Timeout error (storage timeout reset)");
+    });
+    clock.tick(1);
+    clock.tick(4999); // wait 5 seconds
+    fakestorage['2 No Respons/post'].notify();
+    clock.tick(5000); // wait 5 seconds
+    deepEqual(state, "Not called yet", "Check callback state.");
+    clock.tick(5000); // wait 5 seconds
+    fakestorage['2 No Respons/post'].free();
+
+    jio = new JIO({
+      "type": "fake",
+      "id": "3 No Respons"
+    }, {
+      "workspace": {},
+      "default_timeout": 2
+    });
 
-  module("JIO");
+    // tests jio option timeout
+    jio.post({}).always(function (answer) {
+      deepEqual(answer, {
+        "error": "request_timeout",
+        "message": "Operation canceled after around " +
+          "2 milliseconds of inactivity.",
+        "reason": "timeout",
+        "status": 408,
+        "statusText": "Request Timeout"
+      }, "Timeout error (specific default timeout)");
+    });
+    clock.tick(1);
+    clock.tick(1);
+
+    // tests command option timeout
+    jio.post({}, {"timeout": 50}).always(function (answer) {
+      deepEqual(answer, {
+        "error": "request_timeout",
+        "message": "Operation canceled after around " +
+          "50 milliseconds of inactivity.",
+        "reason": "timeout",
+        "status": 408,
+        "statusText": "Request Timeout"
+      }, "Timeout error (command timeout)");
+    });
+    clock.tick(1);
+    clock.tick(49);
+  });
 
   /**
-   * Tests the instance initialization
+   * Tests wrong responses
    */
-  test('Instanciation', function () {
-    expect(1);
-    var jio = jIO.newJio(undefined);
-
-    // tests if jio is an object
-    ok(typeof jio === 'object', 'Init ok!');
+  test('Invalid Response', function () {
+    var clock, jio;
+    expect(2);
+    clock = sinon.useFakeTimers();
+    jio = new JIO({
+      "type": "fake",
+      "id": "1 Invalid Re"
+    }, {
+      "workspace": {}
+    });
 
-    // checks the workspace
-    // XXX nothing to check for the moment, need to be implemented first
+    jio.post({}).always(function (answer) {
+      deepEqual(answer, {
+        "error": "internal_storage_error",
+        "message": "New document id have to be specified",
+        "reason": "invalid response",
+        "status": 551,
+        "statusText": "Internal Storage Error"
+      }, "Invalid Post Response");
+    });
+    clock.tick(1);
+    fakestorage['1 Invalid Re/post'].success();
+    clock.tick(1);
+
+    jio = new JIO({
+      "type": "fake",
+      "id": "2 Invalid Re"
+    }, {
+      "workspace": {}
+    });
 
-    closeAndcleanUpJio(jio);
+    jio.post({}).always(function (answer) {
+      deepEqual(answer, {
+        "error": "internal_storage_error",
+        "message": "Unknown status \"undefined\"",
+        "reason": "invalid response",
+        "status": 551,
+        "statusText": "Internal Storage Error"
+      }, "Invalid Post Error Response");
+    });
+    clock.tick(1);
+    fakestorage['2 Invalid Re/post'].error();
+    clock.tick(1);
   });
 
-  module("JIO Dummy Storage");
-
-  // XXX This will be uncommented when given parameters checking will be implem
-  // /**
-  //  * Tests wrong commands
-  //  */
-  // test('Wrong parameters', function () {
-  //   var result, jio = jIO.newJio({
-  //     "type": "dummy",
-  //     "mode": "normal"
-  //   }, {
-  //     "workspace": {}
-  //   });
-
-  //   try {
-  //     jio.post(); // post(kwargs, [options], [callbacks]);
-  //     result = "No error thrown";
-  //   } catch (e1) {
-  //     result = e1.name + ": " + e1.message;
-  //   }
-  //   deepEqual(
-  //     result,
-  //     "TypeError: JIO().post(): Argument 1 is not of type 'object'",
-  //     "Wrong parameter"
-  //   );
-
-  //   try {
-  //     jio.allDocs(); // allDocs([options], [callbacks]);
-  //     result = "No error thrown";
-  //   } catch (e2) {
-  //     result = e2.name + ": " + e2.message;
-  //   }
-  //   deepEqual(result, "No error thrown", "Good parameter");
-  // });
-
-  // XXX this will be uncommented when validateState will be replaced by a
-  //     simple `throw` in the storage init
-  // /**
-  //  * Tests a storage initialization error
-  //  */
-  // test('Description Error', function () {
-  //   var clock, jio;
-  //   clock = sinon.useFakeTimers();
-  //   jio = jIO.newJio({
-  //     "type": "blue"
-  //   }, {
-  //     "workspace": {}
-  //   });
-
-  //   // Tests wrong storage type
-  //   jio.post({}).always(function (answer) {
-  //     deepEqual(answer, {
-  //       "error": "internal_storage_error",
-  //       "message": "Check if the storage description respects the " +
-  //         "constraints provided by the storage designer. (TypeError: " +
-  //         "Unknown storage 'blue')",
-  //       "reason": "invalid description",
-  //       "status": 551,
-  //       "statusText": "Internal Storage Error"
-  //     }, "Unknown storage");
-  //   });
-  //   clock.tick(1);
-
-  //   // Tests wrong storage description
-  //   jio = jIO.newJio({
-  //     "type": "dummy",
-  //     "mode": "spec error"
-  //   }, {
-  //     "workspace": {}
-  //   });
-
-  //   jio.post({}).always(function (answer) {
-  //     deepEqual(answer, {
-  //       "error": "internal_storage_error",
-  //       "message": "Check if the storage description respects the " +
-  //         "constraints provided by the storage designer. (TypeError: " +
-  //         "Initialization error set by the storage description.)",
-  //       "reason": "invalid description",
-  //       "status": 551,
-  //       "statusText": "Internal Storage Error"
-  //     }, "Initialization error");
-  //   });
-  //   clock.tick(1);
-  // });
-
-
-  // XXX timeout is not implemented yet
-  // /**
-  //  * Tests a command which does not respond
-  //  */
-  // test('No Response or Response Timeout', function () {
-  //   var clock, jio, state;
-  //   expect(5);
-  //   clock = sinon.useFakeTimers();
-  //   jio = jIO.newJio({
-  //     "type": "dummy",
-  //     "mode": "no response"
-  //   }, {
-  //     "workspace": {}
-  //   });
-
-  //   // tests with default timeout
-  //   jio.post({}).always(function (answer) {
-  //     deepEqual(answer, {
-  //       "error": "request_timeout",
-  //       "message": "Operation canceled after around 10000 milliseconds.",
-  //       "reason": "timeout",
-  //       "status": 408,
-  //       "statusText": "Request Timeout"
-  //     }, "Timeout error (default timeout)");
-  //   });
-  //   clock.tick(10000); // wait 10 seconds
-
-  //   jio = jIO.newJio({
-  //     "type": "dummy",
-  //     "mode": "no response + timeout reset",
-  //     "value": 5000 // reset after 5 seconds
-  //   }, {
-  //     "workspace": {}
-  //   });
-
-  //   // tests with storage timeout extension
-  //   state = "Not called yet";
-  //   jio.post({}).always(function (answer) {
-  //     state = "Called";
-  //     deepEqual(answer, {
-  //       "error": "request_timeout",
-  //       "message": "Operation canceled after around 15000 milliseconds.",
-  //       "reason": "timeout",
-  //       "status": 408,
-  //       "statusText": "Request Timeout"
-  //     }, "Timeout error (storage timeout reset)");
-  //   });
-  //   clock.tick(10000); // wait 10 seconds
-  //   deepEqual(state, "Not called yet", "Check callback state.");
-  //   clock.tick(5000); // wait 5 seconds
-
-  //   jio = jIO.newJio({
-  //     "type": "dummy",
-  //     "mode": "no response"
-  //   }, {
-  //     "workspace": {},
-  //     "default_timeout": 2
-  //   });
-
-  //   // tests with jio option timeout
-  //   jio.post({}).always(function (answer) {
-  //     deepEqual(answer, {
-  //       "error": "request_timeout",
-  //       "message": "Operation canceled after around 2 milliseconds.",
-  //       "reason": "timeout",
-  //       "status": 408,
-  //       "statusText": "Request Timeout"
-  //     }, "Timeout error (specific default timeout)");
-  //   });
-  //   clock.tick(2);
-
-  //   // tests with command option timeout
-  //   jio.post({}, {"timeout": 50}).always(function (answer) {
-  //     deepEqual(answer, {
-  //       "error": "request_timeout",
-  //       "message": "Operation canceled after around 50 milliseconds.",
-  //       "reason": "timeout",
-  //       "status": 408,
-  //       "statusText": "Request Timeout"
-  //     }, "Timeout error (command timeout)");
-  //   });
-  //   clock.tick(50);
-  // });
-
-  // /**
-  //  * Tests wrong responses
-  //  */
-  // test('Invalid Response', function () {
-  //   var clock, jio;
-  //   clock = sinon.useFakeTimers();
-  //   jio = jIO.newJio({
-  //     "type": "dummy",
-  //     "mode": "post response no id"
-  //   });
-
-  //   jio.post({}, function (err, response) {
-  //     deepEqual(err || response, {
-  //       "error": "internal_storage_error",
-  //       "message": "New document id have to be specified",
-  //       "reason": "invalid response",
-  //       "status": 551,
-  //       "statusText": "Internal Storage Error"
-  //     }, "Invalid Post Response");
-  //   });
-  //   clock.tick(1000);
-
-  //   closeAndcleanUpJio(jio);
-
-  //   jio = jIO.newJio({
-  //     "type": "dummy",
-  //     "mode": "invalid error response"
-  //   });
-
-  //   jio.post({}, function (err, response) {
-  //     deepEqual(err || response, {
-  //       "error": "internal_storage_error",
-  //       "message": "Unknown status \"undefined\"",
-  //       "reason": "invalid response",
-  //       "status": 551,
-  //       "statusText": "Internal Storage Error"
-  //     }, "Invalid Post Error Response");
-  //   });
-  //   clock.tick(1000);
-
-  //   closeAndcleanUpJio(jio);
-  // });
-
   /**
-   * Tests a valid responses
+   * Tests valid responses
    */
   test('Valid Responses & Callbacks', function () {
-    expect(4);
-    var clock, jio, message;
+    var clock, jio, o = {};
+    expect(7);
     clock = sinon.useFakeTimers();
 
-    jio = jIO.newJio({
-      "type": "dummy",
-      "mode": "normal"
+    jio = new JIO({
+      "type": "fake",
+      "id": "Valid Resp"
+    }, {
+      "workspace": {}
     });
 
     // Tests post command callbacks post(metadata).always(onResponse) +
     // valid response.
-    message = "Post Command: post(metadata).always(function (answer) {..}) + " +
-      "valid response.";
-    jio.post({}, function (err, response) {
-      deepEqual(err || response, {
+    o.message = "Post Command: post(metadata).always(function (answer) {..}) " +
+      "+ valid response.";
+    jio.post({}).always(function (answer) {
+      deepEqual(answer, {
         "ok": true,
-        "id": "document id a"
-      }, message);
+        "id": "document id a",
+        "status": 201,
+        "statusText": "Created"
+      }, o.message);
     });
-    clock.tick(1000);
+    clock.tick(1);
+    fakestorage['Valid Resp/post'].success({"id": "document id a"});
+    clock.tick(1);
 
     // Tests post command callbacks post(metadata).done(onSuccess).fail(onError)
-    message = "Post Command: post(metadata).done(function (answer) {..})." +
+    o.message = "Post Command: post(metadata).done(function (answer) {..})." +
       "fail(function (answer) {..})";
-    jio.post({}, function (answer) {
+    jio.post({}).done(function (answer) {
       deepEqual(answer, {
         "ok": true,
-        "id": "document id a"
-      }, message);
-    }, function (answer) {
-      deepEqual(answer, "Should not fail", message);
+        "id": "document id a",
+        "status": 201,
+        "statusText": "Created"
+      }, o.message);
+    }).fail(function (answer) {
+      deepEqual(answer, "Should not fail", o.message);
     });
-    clock.tick(1000);
+    clock.tick(1);
+    fakestorage['Valid Resp/post'].success({"id": "document id a"});
+    clock.tick(1);
 
     // Tests post command callbacks post(metadata, onResponse)
-    message = "Post Command: post(metadata, function (err, response) {..})";
+    o.message = "Post Command: post(metadata, function (err, response) {..})";
     jio.post({}, function (err, response) {
       if (err) {
-        return deepEqual(err, "Should not fail", message);
+        return deepEqual(err, "Should not fail", o.message);
       }
       deepEqual(response, {
         "ok": true,
-        "id": "document id a"
-      }, message);
+        "id": "document id a",
+        "status": 201,
+        "statusText": "Created"
+      }, o.message);
     });
-    clock.tick(1000);
-
-    closeAndcleanUpJio(jio);
+    clock.tick(1);
+    fakestorage['Valid Resp/post'].success({"id": "document id a"});
+    clock.tick(1);
 
     // Tests post command callbacks post(metadata, onSuccess, onError) + error
     // response.
-    message = "Post Command: post(metadata, function (response) {..}, " +
+    o.message = "Post Command: post(metadata, function (response) {..}, " +
       "function (err) {..}) + valid error response.";
-    jio = jIO.newJio({
-      "type": "dummy",
-      "mode": "always fail"
-    });
 
     jio.post({}, function (response) {
-      deepEqual(response, "Should fail", message);
+      deepEqual(response, "Should fail", o.message);
     }, function (err) {
       deepEqual(err, {
         "status": 409,
@@ -539,183 +410,244 @@
         "error": "conflict",
         "reason": "unknown",
         "message": ""
-      }, message);
+      }, o.message);
     });
-    clock.tick(1000);
-
-    closeAndcleanUpJio(jio);
-  });
-
-  module("JIO Job Management");
-
-  test("Several Jobs at the same time", function () {
-    expect(3);
-    var clock = sinon.useFakeTimers(), jio = jIO.newJio({"type": "dummy"});
-
-    jio.put({"_id": "file1",  "title": "t1"}, spyJioCallback('value', {
-      "ok": true,
-      "id": "file1"
-    }, "job1"));
-    jio.put({"_id": "file2", "title": "t2"}, spyJioCallback('value', {
-      "ok": true,
-      "id": "file2"
-    }, "job2"));
-    jio.put({"_id": "file3", "title": "t3"}, spyJioCallback('value', {
-      "ok": true,
-      "id": "file3"
-    }, "job3"));
-    clock.tick(1000);
-
-    closeAndcleanUpJio(jio);
-  });
+    clock.tick(1);
+    fakestorage['Valid Resp/post'].error('conflict');
+    clock.tick(1);
+
+    // Tests getAttachment command string response
+    jio.getAttachment({"_id": "a", "_attachment": "b"}).
+      always(function (answer) {
+        ok(answer instanceof Blob, "Get Attachment Command: Blob returned");
+      });
+    clock.tick(1);
+    fakestorage['Valid Resp/getAttachment'].success("document id a");
+    clock.tick(1);
+
+    // Tests notify responses
+    o.notified = true;
+    o.message = "Synchronous Notify";
+    jio.post({}).progress(function (answer) {
+      deepEqual(answer, o.answer, o.message);
+    });
+    clock.tick(1);
+    o.answer = undefined;
+    fakestorage['Valid Resp/post'].notify();
+    o.answer = 'hoo';
+    fakestorage['Valid Resp/post'].notify(o.answer);
+    o.answer = 'Forbidden!!!';
+    o.message = 'Notification forbidden after success';
+    setTimeout(fakestorage['Valid Resp/post'].notify, 2);
+    fakestorage['Valid Resp/post'].success();
+    clock.tick(2);
 
-  test("Similar Jobs at the same time (Update)", function () {
-    expect(8);
-    var clock = sinon.useFakeTimers(), jio = jIO.newJio({"type": "dummy"});
-    function compareResults(err, response) {
-      deepEqual(err || response, {"id": "file", "ok": true}, "job ok");
-    }
-    jio.put({"_id": "file", "title": "t"}, compareResults);
-    jio.put({"_id": "file", "title": "t"}, compareResults);
-    jio.put({"_id": "file", "title": "t"}, compareResults);
-    deepEqual(getJioLastJob(jio).id, 1, "Check job queue");
-    clock.tick(1000);
-
-    jio.put({"_id": "file", "content": "content"}, compareResults);
-    jio.remove({"_id": "file", "content": "content"}, compareResults);
-    jio.put({"_id": "file", "content": "content"}, compareResults);
-    deepEqual(getJioLastJob(jio).id, 5, "Check job queue");
-    clock.tick(10000);
-
-    closeAndcleanUpJio(jio);
   });
 
-  test("Same document jobs at the same time (Wait for job(s))", function () {
-    expect(6);
-    var clock = sinon.useFakeTimers(), jio = jIO.newJio({"type": "dummy"});
-
-    function compareResults(err, response) {
-      deepEqual(err || response, {"id": "file", "ok": true}, "job ok");
-    }
+  /**
+   * Tests metadata values
+   */
+  test('Metadata values', function () {
+    expect(9);
+    var o, clock = sinon.useFakeTimers(), jio = new JIO({
+      "type": "fake",
+      "id": "Metadata v"
+    }, {
+      "workspace": {}
+    });
 
-    jio.put({"_id": "file", "content": "content"}, compareResults);
-    deepEqual(
-      getJioLastJob(jio).status.waitforjob,
-      undefined,
-      "Job 1 is not waiting for someone"
-    );
+    o = {};
+
+    o.request = {
+      "_id": undefined,
+      "number": -13,
+      "date": new Date(0),
+      "boolean": true,
+      "array": ['a'],
+      "long_array": ['a', 'b'],
+      "object": {'content': 'c'},
+      "long_object": {'content': 'd', "scheme": "e"},
+      "toJSON": {toJSON: function () {
+        return 'hey!';
+      }},
+      "null": null,
+      "undefined": undefined,
+      "invalid_date": new Date('aoeuh'),
+      "not_finite": Infinity,
+      "empty_array": [],
+      "empty_object": {},
+      "no_content_object": {"e": "f"},
+      "wrong_array": [{}, null, {"blue": "green"}]
+    };
 
-    jio.remove({"_id": "file", "content": "content"}, compareResults);
-    deepEqual(
-      getJioLastJob(jio).status.waitforjob,
-      [1],
-      "Job 2 is wainting for 1"
-    );
+    o.response = {
+      "number": -13,
+      "date": new Date(0).toJSON(),
+      "boolean": true,
+      "array": "a",
+      "long_array": ["a", "b"],
+      "object": "c",
+      "long_object": {"content": "d", "scheme": "e"},
+      "toJSON": "hey!"
+    };
 
-    jio.put({"_id": "file"}, compareResults);
+    jio.post(o.request);
+    clock.tick(1);
     deepEqual(
-      getJioLastJob(jio).status.waitforjob,
-      [1, 2],
-      "Job 3 is waiting for 1 and 2"
+      fakestorage["Metadata v/post"].param,
+      o.response,
+      "Post"
     );
-
-    clock.tick(1000);
-
-    closeAndcleanUpJio(jio);
-  });
-
-  test("Server will be available soon (Wait for time)", function () {
-    expect(2);
-    var clock = sinon.useFakeTimers(), jio;
-    jio = jIO.newJio({
-      "type": "dummy",
-      "mode": "retry",
-      "key": "035139054",
-      "value": 3
+    fakestorage["Metadata v/post"].success();
+    clock.tick(1);
+
+    o.request._id = 'a';
+    o.response._id = 'a';
+    jio.put(o.request);
+    clock.tick(1);
+    deepEqual(fakestorage["Metadata v/put"].param, o.response, "Put");
+    fakestorage["Metadata v/put"].success();
+    clock.tick(1);
+
+    jio.get({
+      "_id": "a"
     });
-    jio.put(
-      {"_id": "file", "content": "content"},
-      {"max_retry": 3},
-      function (err, response) {
-        deepEqual(err || response, {"id": "file", "ok": true}, "Job ok");
-      }
-    );
-    clock.tick(2000);
-
-    deepEqual(dummyStorage['035139054'], 3, "tried 3 times");
-    delete dummyStorage['035139054'];
-
-    closeAndcleanUpJio(jio);
-  });
-
-  test("Restore old Jio", function () {
-
-    var o = {
-      clock: sinon.useFakeTimers(),
-      spy: ospy,
-      tick: otick
+    clock.tick(1);
+    deepEqual(fakestorage["Metadata v/get"].param, {
+      "_id": "a"
+    }, "Get");
+    fakestorage["Metadata v/get"].success();
+    clock.tick(1);
+
+    jio.remove({
+      "_id": "a"
+    });
+    clock.tick(1);
+    deepEqual(fakestorage["Metadata v/remove"].param, {
+      "_id": "a"
+    }, "Remove");
+    fakestorage["Metadata v/remove"].success();
+    clock.tick(1);
+
+    jio.allDocs();
+    clock.tick(1);
+    deepEqual(fakestorage["Metadata v/allDocs"].param, {}, "AllDocs");
+    fakestorage["Metadata v/allDocs"].success();
+    clock.tick(1);
+
+    o.request = {
+      "_id": "a",
+      "_attachment": "body",
+      "_data": "b",
+      "_mimetype": "c"
     };
+    jio.putAttachment(o.request);
+    clock.tick(1);
+    ok(fakestorage["Metadata v/putAttachment"].param._blob instanceof Blob,
+       "Put Attachment + check blob");
+    deepEqual([
+      fakestorage["Metadata v/putAttachment"].param._id,
+      fakestorage["Metadata v/putAttachment"].param._attachment
+    ], ["a", "body"], "Put Attachment + check ids");
+    fakestorage["Metadata v/putAttachment"].success();
+    clock.tick(1);
+
+    o.request._blob = new Blob(['d'], {"type": "e"});
+    delete o.request._mimetype;
+    delete o.request._data;
+    jio.putAttachment(o.request);
+    clock.tick(1);
+    ok(fakestorage["Metadata v/putAttachment"].param._blob === o.request._blob,
+       "Put Attachment with blob + check blob");
+    deepEqual([
+      fakestorage["Metadata v/putAttachment"].param._id,
+      fakestorage["Metadata v/putAttachment"].param._attachment
+    ], ["a", "body"], "Put Attachment with blob + check ids");
+    fakestorage["Metadata v/putAttachment"].success();
+    clock.tick(1);
+  });
 
-    function waitUntilLastJobIs(state) {
-      while (true) {
-        if (getJioLastJob(o.jio) === undefined) {
-          ok(false, "No job have state: " + state);
-          break;
-        }
-        if (getJioLastJob(o.jio).status.label === state) {
-          break;
-        }
-        o.clock.tick(25);
-      }
-    }
-
-    function waitUntilAJobExists(timeout) {
-      var cpt = 0, job = false;
-      while (true) {
-        if (getJioLastJob(o.jio) !== undefined) {
-          job = true;
-          break;
-        }
-        if (cpt >= timeout) {
-          break;
-        }
-        o.clock.tick(25);
-        cpt += 25;
-      }
-      ok(job, "Wait until a job is created");
-    }
+  /**
+   * Tests job retry
+   */
+  test("Job Retry", function () {
+    var clock, jio, state;
+    expect(4);
+    clock = sinon.useFakeTimers();
 
-    o.jio = jIO.newJio({
-      "type": "dummy",
-      "mode": "retry",
-      "key": "12314",
-      "value": 3
+    jio = new JIO({
+      "type": "fake",
+      "id": "1 Job Retry"
+    }, {
+      "workspace": {}
     });
 
-    o.jio_id = o.jio.getId();
-
-    o.jio.put({"_id": "file", "title": "myFile"}, {"max_retry": 3}, o.f);
-    waitUntilLastJobIs("initial"); // "on going" or "wait" should work
-    // xxx also test with waitUntilLastJobIs("on going") ?
-    o.jio.close();
-
-    o.jio = jIO.newJio({
-      "type": "dummy",
-      "mode": "retry",
-      "key": "12314",
-      "value": 3
+    state = "Not called yet";
+    jio.get({"_id": "a"}).always(function (answer) {
+      state = "Called";
+      deepEqual(answer, {
+        "error": "internal_server_error",
+        "message": "",
+        "reason": "unknown",
+        "status": 500,
+        "statusText": "Internal Server Error"
+      }, "Error response");
     });
-    waitUntilAJobExists(30000); // timeout 30 sec
+    clock.tick(1);
+    fakestorage['1 Job Retry/get'].retry('internal_server_error');
+    clock.tick(1);
+    deepEqual(state, "Not called yet", "Check callback state.");
+
+    clock.tick(1999);
+    fakestorage['1 Job Retry/get'].retry('internal_server_error');
+    clock.tick(1);
+    deepEqual(state, "Not called yet", "Check callback state.");
+
+    clock.tick(3999);
+    fakestorage['1 Job Retry/get'].retry('internal_server_error');
+    clock.tick(1);
+    deepEqual(state, "Called", "Check callback state.");
+  });
 
-    deepEqual(getJioLastJob(o.jio).command.label, 'put', 'Job restored');
-    o.clock.tick(2000);
-    ok(getJioLastJob(o.jio) === undefined, "Job executed");
-    o.clock.tick(1000);
+  /**
+   * Tests job workspace
+   */
+  test("Job Workspace", function () {
+    var workspace = {}, clock, jio;
+    expect(2);
 
-    exports.jsonlocalstorage.removeItem("jio/id/" + o.jio_id);
-    exports.jsonlocalstorage.removeItem("jio/job_array/" + o.jio_id);
-    closeAndcleanUpJio(o.jio);
+    clock = sinon.useFakeTimers();
+    jio = new JIO({
+      "type": "fake",
+      "id": "1 Job Worksp"
+    }, {
+      "workspace": workspace
+    });
 
+    jio.get({"_id": "a"}, {"max_retry": 2, "timeout": 12});
+
+    deepEqual(workspace, {
+      "jio/jobs/{\"id\":\"1 Job Worksp\",\"type\":\"fake\"}": jIO.util.
+        uniqueJSONStringify([{
+          "kwargs": {"_id": "a"},
+          "options": {"max_retry": 2, "timeout": 12},
+          "storage_spec": {"type": "fake", "id": "1 Job Worksp"},
+          "method": "get",
+          "created": new Date(0),
+          "tried": 1,
+          "state": "running",
+          "modified": new Date(0),
+          "max_retry": 2,
+          "timeout": 12,
+          "id": 1
+        }])
+    }, 'Check workspace');
+
+    clock.tick(1);
+    fakestorage["1 Job Worksp/get"].success({"_id": "a", "b": "c"});
+    clock.tick(1);
+
+    deepEqual(workspace, {}, 'Check workspace');
   });
 
 }));
-- 
2.30.9