Bug 1022725: add a mock httpd.js Translation provider for tests. r=florian,felipe

This commit is contained in:
Mike de Boer 2014-06-19 16:56:02 +02:00
parent e5e972ff8a
commit 30a432a6e1
7 changed files with 322 additions and 2 deletions

View File

@ -0,0 +1,218 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, Constructor: CC} = Components;
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream");
function handleRequest(req, res) {
try {
reallyHandleRequest(req, res);
} catch (ex) {
res.setStatusLine("1.0", 200, "AlmostOK");
let msg = "Error handling request: " + ex + "\n" + ex.stack;
log(msg);
res.write(msg);
}
}
function log(msg) {
// dump("BING-SERVER-MOCK: " + msg + "\n");
}
const statusCodes = {
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
500: "Internal Server Error",
501: "Not Implemented",
503: "Service Unavailable"
};
function HTTPError(code = 500, message) {
this.code = code;
this.name = statusCodes[code] || "HTTPError";
this.message = message || this.name;
}
HTTPError.prototype = new Error();
HTTPError.prototype.constructor = HTTPError;
function sendError(res, err) {
if (!(err instanceof HTTPError)) {
err = new HTTPError(typeof err == "number" ? err : 500,
err.message || typeof err == "string" ? err : "");
}
res.setStatusLine("1.1", err.code, err.name);
res.write(err.message);
}
function parseQuery(query) {
let ret = {};
for (let param of query.replace(/^[?&]/, "").split("&")) {
param = param.split("=");
if (!param[0])
continue;
ret[unescape(param[0])] = unescape(param[1]);
}
return ret;
}
function getRequestBody(req) {
let avail;
let bytes = [];
let body = new BinaryInputStream(req.bodyInputStream);
while ((avail = body.available()) > 0)
Array.prototype.push.apply(bytes, body.readByteArray(avail));
return String.fromCharCode.apply(null, bytes);
}
function sha1(str) {
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
// `result` is an out parameter, `result.value` will contain the array length.
let result = {};
// `data` is an array of bytes.
let data = converter.convertToByteArray(str, result);
let ch = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
ch.init(ch.SHA1);
ch.update(data, data.length);
let hash = ch.finish(false);
// Return the two-digit hexadecimal code for a byte.
function toHexString(charCode) {
return ("0" + charCode.toString(16)).slice(-2);
}
// Convert the binary hash data to a hex string.
return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
}
function parseXml(body) {
let DOMParser = Cc["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Ci.nsIDOMParser);
let xml = DOMParser.parseFromString(body, "text/xml");
if (xml.documentElement.localName == "parsererror")
throw new Error("Invalid XML");
return xml;
}
function getInputStream(path) {
let file = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get("CurWorkD", Ci.nsILocalFile);
for (let part of path.split("/"))
file.append(part);
let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
fileStream.init(file, 1, 0, false);
return fileStream;
}
function checkAuth(req) {
let err = new Error("Authorization failed");
err.code = 401;
if (!req.hasHeader("Authorization"))
throw new HTTPError(401, "No Authorization header provided.");
let auth = req.getHeader("Authorization");
if (!auth.startsWith("Bearer "))
throw new HTTPError(401, "Invalid Authorization header content: '" + auth + "'");
}
function reallyHandleRequest(req, res) {
log("method: " + req.method);
if (req.method != "POST") {
sendError(res, "Bing only deals with POST requests, not '" + req.method + "'.");
return;
}
let body = getRequestBody(req);
log("body: " + body);
// First, we'll see if we're dealing with an XML body:
let contentType = req.hasHeader("Content-Type") ? req.getHeader("Content-Type") : null;
log("contentType: " + contentType);
if (contentType == "text/xml") {
try {
// For all these requests the client needs to supply the correct
// authentication headers.
checkAuth(req);
let xml = parseXml(body);
let method = xml.documentElement.localName;
log("invoking method: " + method);
// If the requested method is supported, delegate it to its handler.
if (methodHandlers[method])
methodHandlers[method](res, xml);
else
throw new HTTPError(501);
} catch (ex) {
sendError(res, ex, ex.code);
}
} else {
// Not XML, so it must be a query-string.
let params = parseQuery(body);
// Delegate an authentication request to the correct handler.
if ("grant_type" in params && params.grant_type == "client_credentials")
methodHandlers.authenticate(res, params);
else
sendError(res, 501);
}
}
const methodHandlers = {
authenticate: function(res, params) {
// Validate a few required parameters.
if (params.scope != "http://api.microsofttranslator.com") {
sendError(res, "Invalid scope.");
return;
}
if (!params.client_id) {
sendError(res, "Missing client_id param.");
return;
}
if (!params.client_secret) {
sendError(res, "Missing client_secret param.");
return;
}
let content = JSON.stringify({
access_token: "test",
expires_in: 600
});
res.setStatusLine("1.1", 200, "OK");
res.setHeader("Content-Length", String(content.length));
res.setHeader("Content-Type", "application/json");
res.write(content);
},
TranslateArrayRequest: function(res, xml, body) {
let from = xml.querySelector("From").firstChild.nodeValue;
let to = xml.querySelector("To").firstChild.nodeValue
log("translating from '" + from + "' to '" + to + "'");
res.setStatusLine("1.1", 200, "OK");
res.setHeader("Content-Type", "text/xml");
let hash = sha1(body).substr(0, 10);
log("SHA1 hash of content: " + hash);
let inputStream = getInputStream(
"browser/browser/components/translation/test/fixtures/result-" + hash + ".txt");
res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
inputStream.close();
}
};

View File

@ -1,6 +1,10 @@
[DEFAULT]
support-files =
bing.sjs
fixtures/bug1022725-fr.html
fixtures/result-da39a3ee5e.txt
[browser_translation_bing.js]
[browser_translation_fhr.js]
skip-if = true # Needs to wait until bug 1022725.
[browser_translation_infobar.js]
[browser_translation_exceptions.js]

View File

@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Test the Bing Translator client against a mock Bing service, bing.sjs.
"use strict";
const kClientIdPref = "browser.translation.bing.clientIdOverride";
const kClientSecretPref = "browser.translation.bing.apiKeyOverride";
const {BingTranslator} = Cu.import("resource:///modules/translation/BingTranslator.jsm", {});
const {TranslationDocument} = Cu.import("resource:///modules/translation/TranslationDocument.jsm", {});
function test() {
waitForExplicitFinish();
Services.prefs.setCharPref(kClientIdPref, "testClient");
Services.prefs.setCharPref(kClientSecretPref, "testSecret");
// Deduce the Mochitest server address in use from a pref that was pre-processed.
let server = Services.prefs.getCharPref("browser.translation.bing.authURL")
.replace("http://", "");
server = server.substr(0, server.indexOf("/"));
let tab = gBrowser.addTab("http://" + server +
"/browser/browser/components/translation/test/fixtures/bug1022725-fr.html");
gBrowser.selectedTab = tab;
registerCleanupFunction(function () {
gBrowser.removeTab(tab);
Services.prefs.clearUserPref(kClientIdPref);
Services.prefs.clearUserPref(kClientSecretPref);
});
let browser = tab.linkedBrowser;
browser.addEventListener("load", function onload() {
if (browser.currentURI.spec == "about:blank")
return;
browser.removeEventListener("load", onload, true);
let client = new BingTranslator(
new TranslationDocument(browser.contentDocument), "fr", "en");
client.translate().then(
result => {
// XXXmikedeboer; here you would continue the test/ content inspection.
ok(result, "There should be a result.");
finish();
},
error => {
ok(false, "Unexpected Client Error: " + error);
finish();
}
);
}, true);
}

View File

@ -57,7 +57,8 @@ function retrieveTranslationCounts() {
return [0, 0];
}
return [day.get("pageTranslatedCount"), day.get("charactersTranslatedCount")];
// .get() may return `undefined`, which we can't compute.
return [day.get("pageTranslatedCount") || 0, day.get("charactersTranslatedCount") || 0];
});
}

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="fr">
<head>
<!--
- Text retrieved from http://fr.wikipedia.org/wiki/Coupe_du_monde_de_football_de_2014
- at 06/13/2014, Creative Commons Attribution-ShareAlike License.
-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>test</title>
</head>
<body>
<h1>Coupe du monde de football de 2014</h1>
<div>La Coupe du monde de football de 2014 est la 20e édition de la Coupe du monde de football, compétition organisée par la FIFA et qui réunit les trente-deux meilleures sélections nationales. Sa phase finale a lieu à l'été 2014 au Brésil. Avec le pays organisateur, toutes les équipes championnes du monde depuis 1930 (Uruguay, Italie, Allemagne, Angleterre, Argentine, France et Espagne) se sont qualifiées pour cette compétition. Elle est aussi la première compétition internationale de la Bosnie-Herzégovine.</div>
</body>
</html>

View File

@ -0,0 +1,22 @@
<ArrayOfTranslateArrayResponse xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<TranslateArrayResponse>
<From>fr</From>
<OriginalTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>34</a:int>
</OriginalTextSentenceLengths>
<TranslatedText>Football's 2014 World Cup</TranslatedText>
<TranslatedTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>25</a:int>
</TranslatedTextSentenceLengths>
</TranslateArrayResponse>
<TranslateArrayResponse>
<From>fr</From>
<OriginalTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>508</a:int>
</OriginalTextSentenceLengths>
<TranslatedText>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus diam sem, porttitor eget neque sit amet, ultricies posuere metus. Cras placerat rutrum risus, nec dignissim magna dictum vitae. Fusce eleifend fermentum lacinia. Nulla sagittis cursus nibh. Praesent adipiscing, elit at pulvinar dapibus, neque massa tincidunt sapien, eu consectetur lectus metus sit amet odio. Proin blandit consequat porttitor. Pellentesque vehicula justo sed luctus vestibulum. Donec metus.</TranslatedText>
<TranslatedTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a:int>475</a:int>
</TranslatedTextSentenceLengths>
</TranslateArrayResponse>
</ArrayOfTranslateArrayResponse>

View File

@ -200,3 +200,7 @@ user_pref('identity.fxaccounts.auth.uri', 'https://%(server)s/fxa-dummy/');
// Enable logging of APZ test data (see bug 961289).
user_pref('apz.test.logging_enabled', true);
// Make sure Translation won't hit the network.
user_pref("browser.translation.bing.authURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
user_pref("browser.translation.bing.translateArrayURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");