diff --git a/Gruntfile.js b/Gruntfile.js index 9c2b3ef6fd03f871175fc422cd3d3cbe1ba1bf6c..18e22008e06a8d4673320cc105cbfe4f85e2e16d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -181,7 +181,8 @@ module.exports = function (grunt) { 'src/jio.storage/indexeddbstorage.js', 'src/jio.storage/cryptstorage.js', 'src/jio.storage/websqlstorage.js', - 'src/jio.storage/fbstorage.js' + 'src/jio.storage/fbstorage.js', + 'src/jio.storage/youtubestorage.js' ], dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js' // dest: 'jio.js' diff --git a/src/jio.storage/youtubestorage.js b/src/jio.storage/youtubestorage.js new file mode 100644 index 0000000000000000000000000000000000000000..a5d56b25fce84b1b086f1517f89f8d0edc12b73f --- /dev/null +++ b/src/jio.storage/youtubestorage.js @@ -0,0 +1,120 @@ +/** + * Youtube (Data) Storage. Type = "youtube". + */ +/*global jIO, RSVP, UriTemplate, JSON, Query */ +/*jslint nomen: true*/ +(function (jIO, RSVP, UriTemplate, JSON, Query) { + "use strict"; + + var GET_URL = "https://www.googleapis.com/youtube/v3/videos?" + + "part=snippet,statistics,contentDetails{&id}{&key}", + GET_TEMPLATE = UriTemplate.parse(GET_URL), + ALLDOCS_URL = "https://www.googleapis.com/youtube/v3/search?" + + "part=snippet{&pageToken}&q={search}&type=video&maxResults=10{&key}", + ALLDOCS_TEMPLATE = UriTemplate.parse(ALLDOCS_URL); + + function handleError(error, id) { + if (error.target && error.target.status === 404) { + throw new jIO.util.jIOError("Cannot find document: " + id, 404); + } + throw error; + } + + function getValue(query_list, key) { + var i; + for (i = 0; i < query_list.length; i += 1) { + if (query_list[i].key === key) { + return query_list[i].value; + } + } + } + + /** + * The JIO Youtube Storage extension + * + * @class YoutubeStorage + * @constructor + */ + function YoutubeStorage(spec) { + if (spec === undefined || spec.api_key === undefined || + typeof spec.api_key !== 'string') { + throw new TypeError("API Key must be a string " + + "which contains more than one character."); + } + this._api_key = spec.api_key; + return; + } + + YoutubeStorage.prototype.get = function (id) { + var key = this._api_key; + return new RSVP.Queue() + .push(function () { + return jIO.util.ajax({ + type: "GET", + url: GET_TEMPLATE.expand({ + "id": id, + "key": key + }) + }); + }) + .push(function (evt) { + return JSON.parse(evt.target.response || evt.target.responseText); + }) + .push(undefined, function (error) { + if (error.target && error.target.status === 404) { + throw new jIO.util.jIOError("Cannot find document: " + id, 404); + } + throw error; + }); + }; + + YoutubeStorage.prototype.buildQuery = function (options) { + var key, + query; + if (options.query === undefined) { + throw new jIO.util.jIOError("query parameter is required", + 400); + } + key = this._api_key; + query = Query.parseStringToObject(options.query); + return new RSVP.Queue() + .push(function () { + var token, + search; + + if (query.type === "simple") { + search = query.value; + token = ""; + } else { + search = getValue(query.query_list, "q"); + token = getValue(query.query_list, "token"); + } + + return jIO.util.ajax({ + "type": "GET", + "url": ALLDOCS_TEMPLATE.expand({ + "search": search, + "key": key, + "pageToken": token + }) + }); + }) + .push(function (data) { + var obj = JSON.parse(data.target.response || data.target.responseText), + i; + for (i = 0; i < obj.items.length; i += 1) { + obj.items[i].value = {}; + } + obj.items.nextPageToken = obj.nextPageToken; + return obj.items; + + }, handleError); + }; + + YoutubeStorage.prototype.hasCapacity = function (name) { + return ((name === "list") || (name === "include") || (name === "query")); + }; + + jIO.addStorage('youtube', YoutubeStorage); + +}(jIO, RSVP, UriTemplate, JSON, Query)); diff --git a/test/jio.storage/youtubestorage.tests.js b/test/jio.storage/youtubestorage.tests.js new file mode 100644 index 0000000000000000000000000000000000000000..aaceba3a0ba92a57e857b55591bfda500500e80e --- /dev/null +++ b/test/jio.storage/youtubestorage.tests.js @@ -0,0 +1,258 @@ +/*jslint nomen: true */ +/*global Blob, sinon, JSON, Query, SimpleQuery, ComplexQuery*/ +(function (jIO, QUnit, sinon, JSON, Query, SimpleQuery, ComplexQuery) { + "use strict"; + + var test = QUnit.test, + stop = QUnit.stop, + start = QUnit.start, + ok = QUnit.ok, + expect = QUnit.expect, + deepEqual = QUnit.deepEqual, + equal = QUnit.equal, + module = QUnit.module, + query = "", + api_key = "sample_key", + sample_id = "sample_id", + token = "sample_token", + domain = "https://www.googleapis.com/youtube/v3/", + get_reply = { + "kind": "youtube#videoListResponse", + "pageInfo": {"totalResults": 1, "resultsPerPage": 1}, + "items": [{"kind": "youtube#video", "id": "sample_id"}] + }; + + function getSample() { + return JSON.stringify({ + "nextPageToken": "sample_token", + "items": [{"id": "0B4kh3jbjOf5LamRlX21MZ"}] + }); + } + function getSampleCont() { + return JSON.stringify({"items": [{"id": "0B4kh3jbjOf5LamRlVCYXM"}]}); + } + function getUrl() { + return domain + "videos?part=snippet,statistics,contentDetails" + + "&id=" + sample_id + "&key=" + api_key; + } + function listUrl(token) { + return domain + "search?part=snippet&pageToken=" + token + + "&q=" + query + "&type=video&maxResults=10&key=" + api_key; + } + function getItem(id) { + return {"id": id, "value": {}}; + } + function setQuery(key, value) { + return new SimpleQuery({"key": key, "value": value, "type": "simple"}); + } + function getResponse(item, has_token) { + var obj = {"data": {"rows": [], "total_rows": 1}}; + obj.data.rows.push(item); + if (has_token) { + obj.nextPageToken = token; + } + return obj; + } + function error404Tester(fun, encl, blob) { + stop(); + expect(3); + + this.jio[fun]("inexistent", encl ? "enclosure" : undefined, + blob || undefined) + .fail(function (error) { + ok(error instanceof jIO.util.jIOError); + equal(error.message, "Cannot find document: inexistent"); + equal(error.status_code, 404); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + } + + ///////////////////////////////////////////////////////////////// + // Youtube Storage constructor + ///////////////////////////////////////////////////////////////// + module("Youtube Storage.constructor"); + + test("create storage", function () { + var jio = jIO.createJIO({ + type: "youtube", + api_key: api_key + }); + equal(jio.__type, "youtube"); + deepEqual(jio.__storage._api_key, api_key); + }); + + ///////////////////////////////////////////////////////////////// + // Youtube Storage.get + ///////////////////////////////////////////////////////////////// + module("Youtube Storage.get", { + setup: function () { + + this.server = sinon.fakeServer.create(); + this.server.autoRespond = true; + this.server.autoRespondAfter = 5; + + this.jio = jIO.createJIO({ + type: "youtube", + api_key: api_key + }); + }, + teardown: function () { + this.server.restore(); + delete this.server; + } + }); + + test("get inexistent document", function () { + var tester = error404Tester.bind(this); + tester("get"); + }); + + test("get document", function () { + var url = getUrl(), + body = get_reply; + + this.server.respondWith("GET", url, [200, { + "Content-Type": "application/json; charset=UTF-8" + }, JSON.stringify(body) + ]); + stop(); + expect(1); + + this.jio.get(sample_id) + .then(function (result) { + deepEqual(result, get_reply); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + ///////////////////////////////////////////////////////////////// + // Youtube Storage.allDocs + ///////////////////////////////////////////////////////////////// + module("Youtube Storage.allDocs", { + setup: function () { + + this.server = sinon.fakeServer.create(); + this.server.autoRespond = true; + this.server.autoRespondAfter = 5; + + this.jio = jIO.createJIO({ + type: "youtube", + api_key: api_key + }); + }, + teardown: function () { + this.server.restore(); + delete this.server; + } + }); + test("allDocs without query", function () { + stop(); + expect(3); + + this.jio.allDocs() + .fail(function (error) { + ok(error instanceof jIO.util.jIOError); + equal(error.message, "query parameter is required"); + equal(error.status_code, 400); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + test("get all docs", function () { + var object_result_1 = getResponse(getItem("0B4kh3jbjOf5LamRlVCYXM")), + server = this.server; + + this.server.respondWith("GET", listUrl(""), [200, { + }, getSampleCont()]); + stop(); + expect(7); + + this.jio.allDocs({"query": Query.objectToSearchText(setQuery("q", ""))}) + .then(function (res) { + equal(server.requests.length, 1); + equal(server.requests[0].method, "GET"); + equal(server.requests[0].url, listUrl("")); + equal(server.requests[0].status, 200); + equal(server.requests[0].requestBody, undefined); + equal(server.requests[0].responseText, getSampleCont()); + deepEqual(res, object_result_1); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + + test("allDocs with sequential API requests (nextPageToken)", function () { + var context = this, + server = this.server, + object_result_2; + + this.server.respondWith("GET", listUrl("sample_token"), [200, { + }, getSampleCont()]); + this.server.respondWith("GET", listUrl(""), [200, { + }, getSample()]); + stop(); + expect(14); + + object_result_2 = getResponse(getItem("0B4kh3jbjOf5LamRlX21MZ", true)); + + context.jio.allDocs({"query": Query.objectToSearchText(setQuery("q", ""))}) + .then(function (res) { + var query; + equal(server.requests.length, 1); + equal(server.requests[0].method, "GET"); + equal(server.requests[0].url, listUrl("")); + equal(server.requests[0].status, 200); + equal(server.requests[0].requestBody, undefined); + equal(server.requests[0].responseText, getSample()); + deepEqual(res, object_result_2); + + // build complex query with query and token + query = Query.objectToSearchText(new ComplexQuery({ + "operator": "AND", + "type": "complex", + "query_list": [ + setQuery("q", ""), + setQuery("token", token) + ] + })); + + object_result_2 = getResponse(getItem("0B4kh3jbjOf5LamRlVCYXM")); + return context.jio.allDocs({"query": query}); + }) + .then(function (res) { + equal(server.requests.length, 2); + equal(server.requests[1].method, "GET"); + equal(server.requests[1].url, listUrl(token)); + equal(server.requests[1].status, 200); + equal(server.requests[1].requestBody, undefined); + equal(server.requests[1].responseText, getSampleCont()); + deepEqual(res, object_result_2); + }) + .fail(function (error) { + ok(false, error); + }) + .always(function () { + start(); + }); + }); + +}(jIO, QUnit, sinon, JSON, Query, SimpleQuery, ComplexQuery)); diff --git a/test/tests.html b/test/tests.html index 9bdecc94aa2421e17d7a932811ead3c2f628840c..321d67f9c22e6463858ed915f1ce029200a12454 100644 --- a/test/tests.html +++ b/test/tests.html @@ -54,6 +54,7 @@ +