test.js 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  1. /* globals describe, it, before, after */
  2. /* eslint no-loop-func:0 guard-for-in:0 */
  3. // no spam
  4. var logging = require("../lib/logging");
  5. if (process.env.VERBOSE_TEST !== "true" && process.env.TRAVIS !== "true") {
  6. logging.log = logging.debug = logging.warn = logging.error = function() {};
  7. }
  8. var networking = require("../lib/networking");
  9. var helpers = require("../lib/helpers");
  10. var cleaner = require("../lib/cleaner");
  11. var request = require("request");
  12. var config = require("../config");
  13. var server = require("../lib/server");
  14. var assert = require("assert");
  15. var skins = require("../lib/skins");
  16. var cache = require("../lib/cache");
  17. var crc = require("crc").crc32;
  18. var fs = require("fs");
  19. // we don't want tests to fail because of slow internet
  20. config.server.http_timeout *= 3;
  21. var uuids = fs.readFileSync("test/uuids.txt").toString().split(/\r?\n/);
  22. var names = fs.readFileSync("test/usernames.txt").toString().split(/\r?\n/);
  23. // Get a random UUID + name in order to prevent rate limiting
  24. var uuid = uuids[Math.round(Math.random() * (uuids.length - 1))];
  25. var name = names[Math.round(Math.random() * (names.length - 1))];
  26. // Let's hope these will never be assigned
  27. var steve_ids = [
  28. "fffffff0" + "fffffff0" + "fffffff0" + "fffffff0",
  29. "fffffff0" + "fffffff0" + "fffffff1" + "fffffff1",
  30. "fffffff0" + "fffffff1" + "fffffff0" + "fffffff1",
  31. "fffffff0" + "fffffff1" + "fffffff1" + "fffffff0",
  32. "fffffff1" + "fffffff0" + "fffffff0" + "fffffff1",
  33. "fffffff1" + "fffffff0" + "fffffff1" + "fffffff0",
  34. "fffffff1" + "fffffff1" + "fffffff0" + "fffffff0",
  35. "fffffff1" + "fffffff1" + "fffffff1" + "fffffff1",
  36. ];
  37. // Let's hope these will never be assigned
  38. var alex_ids = [
  39. "fffffff0" + "fffffff0" + "fffffff0" + "fffffff1",
  40. "fffffff0" + "fffffff0" + "fffffff1" + "fffffff0",
  41. "fffffff0" + "fffffff1" + "fffffff0" + "fffffff0",
  42. "fffffff0" + "fffffff1" + "fffffff1" + "fffffff1",
  43. "fffffff1" + "fffffff0" + "fffffff0" + "fffffff0",
  44. "fffffff1" + "fffffff0" + "fffffff1" + "fffffff1",
  45. "fffffff1" + "fffffff1" + "fffffff0" + "fffffff1",
  46. "fffffff1" + "fffffff1" + "fffffff1" + "fffffff0",
  47. ];
  48. // generates a 12 character random string
  49. function rid() {
  50. return Math.random().toString(36).substring(2, 14);
  51. }
  52. function getRandomInt(min, max) {
  53. return Math.floor(Math.random() * (max - min + 1)) + min;
  54. }
  55. var ids = [
  56. uuid.toLowerCase(),
  57. name.toLowerCase(),
  58. name.toUpperCase(),
  59. uuid.toUpperCase(),
  60. ];
  61. describe("Crafatar", function() {
  62. // we might have to make 2 HTTP requests
  63. this.timeout(config.server.http_timeout * 2 + 50);
  64. before(function(done) {
  65. console.log("Flushing and waiting for redis ...");
  66. cache.get_redis().flushall(function() {
  67. console.log("Redis flushed!");
  68. // cause I don't know how big hard drives are these days
  69. config.cleaner.disk_limit = Infinity;
  70. config.cleaner.redis_limit = Infinity;
  71. cleaner.run();
  72. done();
  73. });
  74. });
  75. describe("UUID/username", function() {
  76. it("empty username is invalid", function(done) {
  77. assert.strictEqual(helpers.id_valid(""), false);
  78. done();
  79. });
  80. it("non-hex uuid is invalid", function(done) {
  81. assert.strictEqual(helpers.id_valid("g098cb60fa8e427cb299793cbd302c9a"), false);
  82. done();
  83. });
  84. it("empty id is invalid", function(done) {
  85. assert.strictEqual(helpers.id_valid(""), false);
  86. done();
  87. });
  88. it("non-alphanumeric username is invalid", function(done) {
  89. assert.strictEqual(helpers.id_valid("usernäme"), false);
  90. done();
  91. });
  92. it("dashed username is invalid", function(done) {
  93. assert.strictEqual(helpers.id_valid("user-name"), false);
  94. done();
  95. });
  96. it(">16 length username is invalid", function(done) {
  97. assert.strictEqual(helpers.id_valid("ThisNameIsTooLong"), false);
  98. done();
  99. });
  100. it("lowercase uuid is valid", function(done) {
  101. assert.strictEqual(helpers.id_valid("0098cb60fa8e427cb299793cbd302c9a"), true);
  102. done();
  103. });
  104. it("uppercase uuid is valid", function(done) {
  105. assert.strictEqual(helpers.id_valid("1DCEF164FF0A47F2B9A691385C774EE7"), true);
  106. done();
  107. });
  108. it("dashed uuid is valid", function(done) {
  109. assert.strictEqual(helpers.id_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), true);
  110. done();
  111. });
  112. it("16 chars, underscored, capital, numbered username is valid", function(done) {
  113. assert.strictEqual(helpers.id_valid("__niceUs3rname__"), true);
  114. done();
  115. });
  116. it("1 char username is valid", function(done) {
  117. assert.strictEqual(helpers.id_valid("a"), true);
  118. done();
  119. });
  120. it("should not exist (uuid)", function(done) {
  121. var number = getRandomInt(0, 9).toString();
  122. networking.get_profile(rid(), Array(33).join(number), function(err, profile) {
  123. assert.ifError(err);
  124. assert.strictEqual(profile, null);
  125. done();
  126. });
  127. });
  128. it("should not exist (username)", function(done) {
  129. networking.get_username_url(rid(), "Steve", 0, function(err, profile) {
  130. assert.ifError(err);
  131. done();
  132. });
  133. });
  134. });
  135. describe("Avatar", function() {
  136. it("uuid's account should exist, but skin should not", function(done) {
  137. // profile "Alex" - hoping it'll never have a skin
  138. networking.get_profile(rid(), "ec561538f3fd461daff5086b22154bce", function(err, profile) {
  139. assert.ifError(err);
  140. assert.notStrictEqual(profile, null);
  141. networking.get_uuid_info(profile, "CAPE", function(url) {
  142. assert.strictEqual(url, null);
  143. done();
  144. });
  145. });
  146. });
  147. it("Username should default to MHF_Steve", function(done) {
  148. assert.strictEqual(skins.default_skin("TestUser"), "mhf_steve");
  149. done();
  150. });
  151. for (var a in alex_ids) {
  152. var alexid = alex_ids[a];
  153. (function(alex_id) {
  154. it("UUID " + alex_id + " should default to MHF_Alex", function(done) {
  155. assert.strictEqual(skins.default_skin(alex_id), "mhf_alex");
  156. done();
  157. });
  158. }(alexid));
  159. }
  160. for (var s in steve_ids) {
  161. var steveid = steve_ids[s];
  162. (function(steve_id) {
  163. it("UUID " + steve_id + " should default to MHF_Steve", function(done) {
  164. assert.strictEqual(skins.default_skin(steve_id), "mhf_steve");
  165. done();
  166. });
  167. }(steveid));
  168. }
  169. });
  170. describe("Errors", function() {
  171. it("should time out on uuid info download", function(done) {
  172. var original_timeout = config.server.http_timeout;
  173. config.server.http_timeout = 1;
  174. networking.get_profile(rid(), "069a79f444e94726a5befca90e38aaf5", function(err, profile) {
  175. assert.notStrictEqual(["ETIMEDOUT", "ESOCKETTIMEDOUT"].indexOf(err.code), -1);
  176. config.server.http_timeout = original_timeout;
  177. done();
  178. });
  179. });
  180. it("should time out on username info download", function(done) {
  181. var original_timeout = config.server.http_timeout;
  182. config.server.http_timeout = 1;
  183. networking.get_username_url(rid(), "jomo", 0, function(err, url) {
  184. assert.notStrictEqual(["ETIMEDOUT", "ESOCKETTIMEDOUT"].indexOf(err.code), -1);
  185. config.server.http_timeout = original_timeout;
  186. done();
  187. });
  188. });
  189. it("should time out on skin download", function(done) {
  190. var original_timeout = config.http_timeout;
  191. config.server.http_timeout = 1;
  192. networking.get_from(rid(), "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
  193. assert.notStrictEqual(["ETIMEDOUT", "ESOCKETTIMEDOUT"].indexOf(error.code), -1);
  194. config.server.http_timeout = original_timeout;
  195. done();
  196. });
  197. });
  198. it("should not find the skin", function(done) {
  199. assert.doesNotThrow(function() {
  200. networking.get_from(rid(), "http://textures.minecraft.net/texture/this-does-not-exist", function(img, response, err) {
  201. assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
  202. done();
  203. });
  204. });
  205. });
  206. it("should not find the file", function(done) {
  207. skins.open_skin(rid(), "non/existent/path", function(err, img) {
  208. assert(err);
  209. done();
  210. });
  211. });
  212. });
  213. describe("Server", function() {
  214. // throws Exception when default headers are not in res.headers
  215. function assert_headers(res) {
  216. assert(res.headers["content-type"]);
  217. assert("" + res.headers["response-time"]);
  218. assert(res.headers["x-request-id"]);
  219. assert.equal(res.headers["access-control-allow-origin"], "*");
  220. assert.equal(res.headers["cache-control"], "max-age=" + config.caching.browser);
  221. }
  222. // throws Exception when +url+ is requested with +etag+
  223. // and it does not return 304 without a body
  224. function assert_cache(url, etag, callback) {
  225. request.get(url, {
  226. headers: {
  227. "If-None-Match": etag,
  228. },
  229. }, function(error, res, body) {
  230. assert.ifError(error);
  231. assert.ifError(body);
  232. assert.equal(res.statusCode, 304);
  233. assert_headers(res);
  234. callback();
  235. });
  236. }
  237. before(function(done) {
  238. server.boot(function() {
  239. done();
  240. });
  241. });
  242. it("should return 405 Method Not Allowed for POST", function(done) {
  243. request.post("http://localhost:3000", function(error, res, body) {
  244. assert.ifError(error);
  245. assert.strictEqual(res.statusCode, 405);
  246. done();
  247. });
  248. });
  249. it("should return correct HTTP response for home page", function(done) {
  250. var url = "http://localhost:3000";
  251. request.get(url, function(error, res, body) {
  252. assert.ifError(error);
  253. assert.strictEqual(res.statusCode, 200);
  254. assert_headers(res);
  255. assert(res.headers.etag);
  256. assert.strictEqual(res.headers["content-type"], "text/html; charset=utf-8");
  257. assert.strictEqual(res.headers.etag, '"' + crc(body) + '"');
  258. assert(body);
  259. assert_cache(url, res.headers.etag, function() {
  260. done();
  261. });
  262. });
  263. });
  264. it("should return correct HTTP response for assets", function(done) {
  265. var url = "http://localhost:3000/stylesheets/style.css";
  266. request.get(url, function(error, res, body) {
  267. assert.ifError(error);
  268. assert.strictEqual(res.statusCode, 200);
  269. assert_headers(res);
  270. assert(res.headers.etag);
  271. assert.strictEqual(res.headers["content-type"], "text/css");
  272. assert.strictEqual(res.headers.etag, '"' + crc(body) + '"');
  273. assert(body);
  274. assert_cache(url, res.headers.etag, function() {
  275. done();
  276. });
  277. });
  278. });
  279. it("should return correct HTTP response for URL encoded URLs", function(done) {
  280. var url = "http://localhost:3000/%61%76%61%74%61%72%73/%6a%6f%6d%6f"; // avatars/jomo
  281. request.get(url, function(error, res, body) {
  282. assert.ifError(error);
  283. assert.strictEqual(res.statusCode, 201);
  284. assert_headers(res);
  285. assert(res.headers.etag);
  286. assert.strictEqual(res.headers["content-type"], "image/png");
  287. assert(body);
  288. done();
  289. });
  290. });
  291. it("should not fail on simultaneous requests", function(done) {
  292. // do not change "constructor" !
  293. // it's a reserved property name, we're testing for that
  294. var sids = ["696a82ce41f44b51aa31b8709b8686f0", "constructor"];
  295. for (var j in sids) {
  296. var id = sids[j];
  297. var url = "http://localhost:3000/avatars/" + id;
  298. // 10 requests at once
  299. var requests = 10;
  300. var finished = 0;
  301. function partDone() {
  302. finished++;
  303. if (requests === finished) {
  304. done();
  305. }
  306. }
  307. function req() {
  308. request.get(url, function(error, res, body) {
  309. assert.ifError(error);
  310. assert.strictEqual(res.statusCode === 201 || res.statusCode === 200, true);
  311. assert_headers(res);
  312. assert(res.headers.etag);
  313. assert.strictEqual(res.headers["content-type"], "image/png");
  314. assert(body);
  315. partDone();
  316. });
  317. }
  318. // make simultanous requests
  319. for (var k = 0; k < requests; k++) {
  320. req(k);
  321. }
  322. }
  323. });
  324. var server_tests = {
  325. "avatar with existing username": {
  326. url: "http://localhost:3000/avatars/jeb_?size=16",
  327. crc32: [3337292777],
  328. },
  329. "avatar with non-existent username": {
  330. url: "http://localhost:3000/avatars/0?size=16",
  331. crc32: [2416827277, 1243826040],
  332. },
  333. "avatar with non-existent username defaulting to mhf_alex": {
  334. url: "http://localhost:3000/avatars/0?size=16&default=mhf_alex",
  335. crc32: [862751081, 809395677],
  336. },
  337. "avatar with non-existent username defaulting to username": {
  338. url: "http://localhost:3000/avatars/0?size=16&default=jeb_",
  339. crc32: [0],
  340. redirect: "/avatars/jeb_?size=16",
  341. },
  342. "avatar with non-existent username defaulting to uuid": {
  343. url: "http://localhost:3000/avatars/0?size=16&default=853c80ef3c3749fdaa49938b674adae6",
  344. crc32: [0],
  345. redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
  346. },
  347. "avatar with non-existent username defaulting to url": {
  348. url: "http://localhost:3000/avatars/0?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  349. crc32: [0],
  350. redirect: "http://example.com/CaseSensitive",
  351. },
  352. "overlay avatar with existing username": {
  353. url: "http://localhost:3000/avatars/jeb_?size=16&overlay",
  354. crc32: [1710265722],
  355. },
  356. "overlay avatar with non-existent username": {
  357. url: "http://localhost:3000/avatars/0?size=16&overlay",
  358. crc32: [2416827277, 1243826040],
  359. },
  360. "overlay avatar with non-existent username defaulting to mhf_alex": {
  361. url: "http://localhost:3000/avatars/0?size=16&overlay&default=mhf_alex",
  362. crc32: [862751081, 809395677],
  363. },
  364. "overlay avatar with non-existent username defaulting to username": {
  365. url: "http://localhost:3000/avatars/0?size=16&overlay&default=jeb_",
  366. crc32: [0],
  367. redirect: "/avatars/jeb_?size=16&overlay=",
  368. },
  369. "overlay avatar with non-existent username defaulting to uuid": {
  370. url: "http://localhost:3000/avatars/0?size=16&overlay&default=853c80ef3c3749fdaa49938b674adae6",
  371. crc32: [0],
  372. redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay=",
  373. },
  374. "overlay avatar with non-existent username defaulting to url": {
  375. url: "http://localhost:3000/avatars/0?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  376. crc32: [0],
  377. redirect: "http://example.com/CaseSensitive",
  378. },
  379. "avatar with existing uuid": {
  380. url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
  381. crc32: [3337292777],
  382. },
  383. "avatar with non-existent uuid": {
  384. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16",
  385. crc32: [2416827277, 1243826040],
  386. },
  387. "avatar with non-existent uuid defaulting to mhf_alex": {
  388. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=mhf_alex",
  389. crc32: [862751081, 809395677],
  390. },
  391. "avatar with non-existent uuid defaulting to username": {
  392. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_",
  393. crc32: [0],
  394. redirect: "/avatars/jeb_?size=16",
  395. },
  396. "avatar with non-existent uuid defaulting to uuid": {
  397. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
  398. crc32: [0],
  399. redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
  400. },
  401. "avatar with non-existent uuid defaulting to url": {
  402. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  403. crc32: [0],
  404. redirect: "http://example.com/CaseSensitive",
  405. },
  406. "overlay avatar with existing uuid": {
  407. url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay",
  408. crc32: [1710265722],
  409. },
  410. "overlay avatar with non-existent uuid": {
  411. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay",
  412. crc32: [2416827277, 1243826040],
  413. },
  414. "overlay avatar with non-existent uuid defaulting to mhf_alex": {
  415. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=mhf_alex",
  416. crc32: [862751081, 809395677],
  417. },
  418. "overlay avatar with non-existent uuid defaulting to username": {
  419. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_",
  420. crc32: [0],
  421. redirect: "/avatars/jeb_?size=16",
  422. },
  423. "overlay avatar with non-existent uuid defaulting to uuid": {
  424. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
  425. crc32: [0],
  426. redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
  427. },
  428. "overlay avatar with non-existent uuid defaulting to url": {
  429. url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  430. crc32: [0],
  431. redirect: "http://example.com/CaseSensitive",
  432. },
  433. "cape with existing username": {
  434. url: "http://localhost:3000/capes/notch",
  435. crc32: [2556702429],
  436. },
  437. "cape with non-existent username": {
  438. url: "http://localhost:3000/capes/0",
  439. crc32: [0],
  440. },
  441. "cape with non-existent username defaulting to url": {
  442. url: "http://localhost:3000/capes/0?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  443. crc32: [0],
  444. redirect: "http://example.com/CaseSensitive",
  445. },
  446. "cape with existing uuid": {
  447. url: "http://localhost:3000/capes/069a79f444e94726a5befca90e38aaf5",
  448. crc32: [2556702429],
  449. },
  450. "cape with non-existent uuid": {
  451. url: "http://localhost:3000/capes/00000000000000000000000000000000",
  452. crc32: [0],
  453. },
  454. "cape with non-existent uuid defaulting to url": {
  455. url: "http://localhost:3000/capes/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  456. crc32: [0],
  457. redirect: "http://example.com/CaseSensitive",
  458. },
  459. "skin with existing username": {
  460. url: "http://localhost:3000/skins/jeb_",
  461. crc32: [26500336],
  462. },
  463. "skin with non-existent username": {
  464. url: "http://localhost:3000/skins/0",
  465. crc32: [981937087],
  466. },
  467. "skin with non-existent username defaulting to mhf_alex": {
  468. url: "http://localhost:3000/skins/0?default=mhf_alex",
  469. crc32: [2298915739],
  470. },
  471. "skin with non-existent username defaulting to username": {
  472. url: "http://localhost:3000/skins/0?size=16&default=jeb_",
  473. crc32: [0],
  474. redirect: "/skins/jeb_?size=16",
  475. },
  476. "skin with non-existent username defaulting to uuid": {
  477. url: "http://localhost:3000/skins/0?size=16&default=853c80ef3c3749fdaa49938b674adae6",
  478. crc32: [0],
  479. redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16",
  480. },
  481. "skin with non-existent username defaulting to url": {
  482. url: "http://localhost:3000/skins/0?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  483. crc32: [0],
  484. redirect: "http://example.com/CaseSensitive",
  485. },
  486. "skin with existing uuid": {
  487. url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6",
  488. crc32: [26500336],
  489. },
  490. "skin with non-existent uuid": {
  491. url: "http://localhost:3000/skins/00000000000000000000000000000000",
  492. crc32: [981937087],
  493. },
  494. "skin with non-existent uuid defaulting to mhf_alex": {
  495. url: "http://localhost:3000/skins/00000000000000000000000000000000?default=mhf_alex",
  496. crc32: [2298915739],
  497. },
  498. "skin with non-existent uuid defaulting to username": {
  499. url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=jeb_",
  500. crc32: [0],
  501. redirect: "/skins/jeb_?size=16",
  502. },
  503. "skin with non-existent uuid defaulting to uuid": {
  504. url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
  505. crc32: [0],
  506. redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16",
  507. },
  508. "skin with non-existent uuid defaulting to url": {
  509. url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  510. crc32: [0],
  511. redirect: "http://example.com/CaseSensitive",
  512. },
  513. "head render with existing username": {
  514. url: "http://localhost:3000/renders/head/jeb_?scale=2",
  515. crc32: [3487896679, 3001090792],
  516. },
  517. "head render with non-existent username": {
  518. url: "http://localhost:3000/renders/head/0?scale=2",
  519. crc32: [3257141069, 214248305],
  520. },
  521. "head render with non-existent username defaulting to mhf_alex": {
  522. url: "http://localhost:3000/renders/head/0?scale=2&default=mhf_alex",
  523. crc32: [263450586, 3116770561],
  524. },
  525. "head render with non-existent username defaulting to username": {
  526. url: "http://localhost:3000/avatars/0?scale=2&default=jeb_",
  527. crc32: [0],
  528. redirect: "/avatars/jeb_?scale=2",
  529. },
  530. "head render with non-existent username defaulting to uuid": {
  531. url: "http://localhost:3000/avatars/0?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
  532. crc32: [0],
  533. redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?scale=2",
  534. },
  535. "head render with non-existent username defaulting to url": {
  536. url: "http://localhost:3000/renders/head/0?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  537. crc32: [0],
  538. redirect: "http://example.com/CaseSensitive",
  539. },
  540. "overlay head render with existing username": {
  541. url: "http://localhost:3000/renders/head/jeb_?scale=2&overlay",
  542. crc32: [762377383, 1726474987],
  543. },
  544. "overlay head render with non-existent username": {
  545. url: "http://localhost:3000/renders/head/0?scale=2&overlay",
  546. crc32: [3257141069, 214248305],
  547. },
  548. "overlay head render with non-existent username defaulting to mhf_alex": {
  549. url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=mhf_alex",
  550. crc32: [263450586, 3116770561],
  551. },
  552. "overlay head render with non-existent username defaulting to username": {
  553. url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=jeb_",
  554. crc32: [0],
  555. redirect: "/renders/head/jeb_?scale=2&overlay=",
  556. },
  557. "overlay head render with non-existent username defaulting to uuid": {
  558. url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
  559. crc32: [0],
  560. redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
  561. },
  562. "overlay head render with non-existent username defaulting to url": {
  563. url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  564. crc32: [0],
  565. redirect: "http://example.com/CaseSensitive",
  566. },
  567. "head render with existing uuid": {
  568. url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
  569. crc32: [3487896679, 3001090792],
  570. },
  571. "head render with non-existent uuid": {
  572. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2",
  573. crc32: [3257141069, 214248305],
  574. },
  575. "head render with non-existent uuid defaulting to mhf_alex": {
  576. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=mhf_alex",
  577. crc32: [263450586, 3116770561],
  578. },
  579. "head render with non-existent uuid defaulting to username": {
  580. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=jeb_",
  581. crc32: [0],
  582. redirect: "/renders/head/jeb_?scale=2",
  583. },
  584. "head render with non-existent uuid defaulting to uuid": {
  585. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
  586. crc32: [0],
  587. redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
  588. },
  589. "head render with non-existent uuid defaulting to url": {
  590. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  591. crc32: [0],
  592. redirect: "http://example.com/CaseSensitive",
  593. },
  594. "overlay head render with existing uuid": {
  595. url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
  596. crc32: [762377383, 1726474987],
  597. },
  598. "overlay head render with non-existent uuid": {
  599. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay",
  600. crc32: [3257141069, 214248305],
  601. },
  602. "overlay head render with non-existent uuid defaulting to mhf_alex": {
  603. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
  604. crc32: [263450586, 3116770561],
  605. },
  606. "overlay head with non-existent uuid defaulting to username": {
  607. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=jeb_",
  608. crc32: [0],
  609. redirect: "/renders/head/jeb_?scale=2&overlay=",
  610. },
  611. "overlay head with non-existent uuid defaulting to uuid": {
  612. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
  613. crc32: [0],
  614. redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
  615. },
  616. "overlay head render with non-existent uuid defaulting to url": {
  617. url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  618. crc32: [0],
  619. redirect: "http://example.com/CaseSensitive",
  620. },
  621. "body render with existing username": {
  622. url: "http://localhost:3000/renders/body/jeb_?scale=2",
  623. crc32: [3127075871, 2595192206],
  624. },
  625. "body render with non-existent username": {
  626. url: "http://localhost:3000/renders/body/0?scale=2",
  627. crc32: [1046655221, 1620063267],
  628. },
  629. "body render with non-existent username defaulting to mhf_alex": {
  630. url: "http://localhost:3000/renders/body/0?scale=2&default=mhf_alex",
  631. crc32: [549240598, 3952648540],
  632. },
  633. "body render with non-existent username defaulting to username": {
  634. url: "http://localhost:3000/renders/body/0?scale=2&default=jeb_",
  635. crc32: [0],
  636. redirect: "/renders/body/jeb_?scale=2",
  637. },
  638. "body render with non-existent username defaulting to uuid": {
  639. url: "http://localhost:3000/renders/body/0?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
  640. crc32: [0],
  641. redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
  642. },
  643. "body render with non-existent username defaulting to url": {
  644. url: "http://localhost:3000/renders/body/0?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  645. crc32: [0],
  646. redirect: "http://example.com/CaseSensitive",
  647. },
  648. "overlay body render with existing username": {
  649. url: "http://localhost:3000/renders/body/jeb_?scale=2&overlay",
  650. crc32: [699892097, 2732138694],
  651. },
  652. "overlay body render with non-existent username": {
  653. url: "http://localhost:3000/renders/body/0?scale=2&overlay",
  654. crc32: [1046655221, 1620063267],
  655. },
  656. "overlay body render with non-existent username defaulting to mhf_alex": {
  657. url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=mhf_alex",
  658. crc32: [549240598, 3952648540],
  659. },
  660. "overlay body render with non-existent username defaulting to username": {
  661. url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=jeb_",
  662. crc32: [0],
  663. redirect: "/renders/body/jeb_?scale=2&overlay=",
  664. },
  665. "overlay body render with non-existent username defaulting to uuid": {
  666. url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
  667. crc32: [0],
  668. redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
  669. },
  670. "overlay body render with non-existent username defaulting to url": {
  671. url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  672. crc32: [0],
  673. redirect: "http://example.com/CaseSensitive",
  674. },
  675. "body render with existing uuid": {
  676. url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
  677. crc32: [3127075871, 2595192206],
  678. },
  679. "body render with non-existent uuid": {
  680. url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2",
  681. crc32: [1046655221, 1620063267],
  682. },
  683. "body render with non-existent uuid defaulting to mhf_alex": {
  684. url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=mhf_alex",
  685. crc32: [549240598, 3952648540],
  686. },
  687. "body render with non-existent uuid defaulting to username": {
  688. url: "http://localhost:3000/renders/body/0?scale=2&default=jeb_",
  689. crc32: [0],
  690. redirect: "/renders/body/jeb_?scale=2",
  691. },
  692. "body render with non-existent uuid defaulting to uuid": {
  693. url: "http://localhost:3000/renders/body/0?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
  694. crc32: [0],
  695. redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
  696. },
  697. "body render with non-existent uuid defaulting to url": {
  698. url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  699. crc32: [0],
  700. redirect: "http://example.com/CaseSensitive",
  701. },
  702. "overlay body render with existing uuid": {
  703. url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
  704. crc32: [699892097, 2732138694],
  705. },
  706. "overlay body render with non-existent uuid": {
  707. url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay",
  708. crc32: [1046655221, 1620063267],
  709. },
  710. "overlay body render with non-existent uuid defaulting to mhf_alex": {
  711. url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
  712. crc32: [549240598, 3952648540],
  713. },
  714. "overlay body render with non-existent uuid defaulting to url": {
  715. url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
  716. crc32: [0],
  717. redirect: "http://example.com/CaseSensitive",
  718. },
  719. };
  720. for (var description in server_tests) {
  721. var loc = server_tests[description];
  722. (function(location) {
  723. it("should return correct HTTP response for " + description, function(done) {
  724. request.get(location.url, {followRedirect: false, encoding: null}, function(error, res, body) {
  725. assert.ifError(error);
  726. assert_headers(res);
  727. assert(res.headers["x-storage-type"]);
  728. var hash = crc(body);
  729. var matches = false;
  730. for (var c = 0; c < location.crc32.length; c++) {
  731. if (location.crc32[c] === hash) {
  732. matches = true;
  733. break;
  734. }
  735. }
  736. try {
  737. assert(matches);
  738. } catch(e) {
  739. throw new Error(hash + " != " + location.crc32 + " | " + body.toString("base64"));
  740. }
  741. assert.strictEqual(res.headers.location, location.redirect);
  742. if (location.crc32[0] === 0) {
  743. assert.strictEqual(res.statusCode, location.redirect ? 307 : 404);
  744. assert.ifError(res.headers.etag); // etag must not be present on non-200
  745. assert.strictEqual(res.headers["content-type"], "text/plain");
  746. done();
  747. } else {
  748. assert.strictEqual(res.headers["content-type"], "image/png");
  749. assert.strictEqual(res.statusCode, res.headers["x-storage-type"] === "downloaded" ? 201 : 200);
  750. assert(res.headers.etag);
  751. assert.strictEqual(res.headers.etag, '"' + hash + '"');
  752. assert_cache(location.url, res.headers.etag, function() {
  753. done();
  754. });
  755. }
  756. });
  757. });
  758. }(loc));
  759. }
  760. it("should update the username skin type on uuid request", function(done) {
  761. /*eslint-disable handle-callback-err */
  762. request.get("http://localhost:3000/renders/body/mhf_alex", function(error, res, body) {
  763. cache.get_details("mhf_alex", function(err, details) {
  764. assert.strictEqual(details.slim, false);
  765. request.get("http://localhost:3000/renders/body/6ab4317889fd490597f60f67d9d76fd9", function(uerror, ures, ubody) {
  766. cache.get_details("mhf_alex", function(cerr, cdetails) {
  767. /*eslint-enable handle-callback-err */
  768. assert.strictEqual(cdetails.slim, true);
  769. done();
  770. });
  771. });
  772. });
  773. });
  774. });
  775. it("should return 304 on server error", function(done) {
  776. var original_debug = config.server.debug_enabled;
  777. var original_timeout = config.server.http_timeout;
  778. config.server.debug_enabled = false;
  779. config.server.http_timeout = 1;
  780. request.get({url: "http://localhost:3000/avatars/whatever", headers: {"If-None-Match": '"some-etag"'}}, function(error, res, body) {
  781. assert.ifError(error);
  782. assert.ifError(body);
  783. assert.strictEqual(res.statusCode, 304);
  784. config.server.debug_enabled = original_debug;
  785. config.server.http_timeout = original_timeout;
  786. done();
  787. });
  788. });
  789. it("should return a 422 (invalid size)", function(done) {
  790. var size = config.avatars.max_size + 1;
  791. request.get("http://localhost:3000/avatars/Jake_0?size=" + size, function(error, res, body) {
  792. assert.ifError(error);
  793. assert.strictEqual(res.statusCode, 422);
  794. done();
  795. });
  796. });
  797. it("should return a 422 (invalid scale)", function(done) {
  798. var scale = config.renders.max_scale + 1;
  799. request.get("http://localhost:3000/renders/head/Jake_0?scale=" + scale, function(error, res, body) {
  800. assert.ifError(error);
  801. assert.strictEqual(res.statusCode, 422);
  802. done();
  803. });
  804. });
  805. it("should return a 422 (invalid render type)", function(done) {
  806. request.get("http://localhost:3000/renders/invalid/Jake_0", function(error, res, body) {
  807. assert.ifError(error);
  808. assert.strictEqual(res.statusCode, 422);
  809. done();
  810. });
  811. });
  812. // testing all paths for Invalid UserID
  813. var locations = ["avatars", "skins", "capes", "renders/body", "renders/head"];
  814. for (var l in locations) {
  815. loc = locations[l];
  816. (function(location) {
  817. it("should return a 422 (invalid id " + location + ")", function(done) {
  818. request.get("http://localhost:3000/" + location + "/thisisaninvaliduuid", function(error, res, body) {
  819. assert.ifError(error);
  820. assert.strictEqual(res.statusCode, 422);
  821. done();
  822. });
  823. });
  824. it("should return a 404 (invalid path " + location + ")", function(done) {
  825. request.get("http://localhost:3000/" + location + "/853c80ef3c3749fdaa49938b674adae6/invalid", function(error, res, body) {
  826. assert.ifError(error);
  827. assert.strictEqual(res.statusCode, 404);
  828. done();
  829. });
  830. });
  831. }(loc));
  832. }
  833. after(function(done) {
  834. server.close(function() {
  835. done();
  836. });
  837. });
  838. });
  839. // we have to make sure that we test both a 32x64 and 64x64 skin
  840. describe("Networking: Render", function() {
  841. it("should not fail (username, 32x64 skin)", function(done) {
  842. helpers.get_render(rid(), "md_5", 6, true, true, function(err, hash, img) {
  843. assert.strictEqual(err, null);
  844. done();
  845. });
  846. });
  847. it("should not fail (username, 64x64 skin)", function(done) {
  848. helpers.get_render(rid(), "Jake_0", 6, true, true, function(err, hash, img) {
  849. assert.strictEqual(err, null);
  850. done();
  851. });
  852. });
  853. });
  854. describe("Networking: Cape", function() {
  855. it("should not fail (guaranteed cape)", function(done) {
  856. helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
  857. assert.strictEqual(err, null);
  858. done();
  859. });
  860. });
  861. it("should already exist", function(done) {
  862. before(function() {
  863. cache.get_redis().flushall();
  864. });
  865. helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
  866. assert.strictEqual(err, null);
  867. done();
  868. });
  869. });
  870. it("should not be found", function(done) {
  871. helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
  872. assert.ifError(err);
  873. assert.strictEqual(img, null);
  874. done();
  875. });
  876. });
  877. });
  878. describe("Networking: Skin", function() {
  879. it("should not fail", function(done) {
  880. helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
  881. assert.strictEqual(err, null);
  882. done();
  883. });
  884. });
  885. it("should already exist", function(done) {
  886. before(function() {
  887. cache.get_redis().flushall();
  888. });
  889. helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
  890. assert.strictEqual(err, null);
  891. done();
  892. });
  893. });
  894. });
  895. // DRY with uuid and username tests
  896. for (var i in ids) {
  897. var iid = ids[i];
  898. var iid_type = iid.length > 16 ? "uuid" : "name";
  899. // needs an anonymous function because id and id_type aren't constant
  900. (function(n, id, id_type) {
  901. // Mojang's UUID rate limiting is case-insensitive
  902. // so we don't run UUID tests twice
  903. if(n < 3) {
  904. describe("Networking: Avatar", function() {
  905. before(function() {
  906. cache.get_redis().flushall();
  907. console.log("\n\nRunning tests with " + id_type + " '" + id + "'\n\n");
  908. });
  909. it("should be downloaded", function(done) {
  910. helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
  911. assert.ifError(err);
  912. assert.strictEqual(status, 2);
  913. done();
  914. });
  915. });
  916. it("should be cached", function(done) {
  917. helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
  918. assert.ifError(err);
  919. assert.strictEqual(status === 0 || status === 1, true);
  920. done();
  921. });
  922. });
  923. if (id.length > 16) {
  924. // can't run 'checked' test due to Mojang's rate limits :(
  925. } else {
  926. it("should be checked", function(done) {
  927. var original_cache_time = config.caching.local;
  928. config.caching.local = 0;
  929. helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
  930. assert.ifError(err);
  931. assert.strictEqual(status, 3);
  932. config.caching.local = original_cache_time;
  933. done();
  934. });
  935. });
  936. }
  937. });
  938. describe("Networking: Skin", function() {
  939. it("should not fail (uuid)", function(done) {
  940. helpers.get_skin(rid(), id, function(err, hash, status, img) {
  941. assert.strictEqual(err, null);
  942. done();
  943. });
  944. });
  945. });
  946. describe("Networking: Render", function() {
  947. it("should not fail (full body)", function(done) {
  948. helpers.get_render(rid(), id, 6, true, true, function(err, hash, img) {
  949. assert.ifError(err);
  950. done();
  951. });
  952. });
  953. it("should not fail (only head)", function(done) {
  954. helpers.get_render(rid(), id, 6, true, false, function(err, hash, img) {
  955. assert.ifError(err);
  956. done();
  957. });
  958. });
  959. });
  960. describe("Networking: Cape", function() {
  961. it("should not fail (possible cape)", function(done) {
  962. helpers.get_cape(rid(), id, function(err, hash, status, img) {
  963. assert.ifError(err);
  964. done();
  965. });
  966. });
  967. });
  968. }
  969. describe("Errors", function() {
  970. before(function() {
  971. if(n >= 3) {
  972. // Notice for tests skipped above
  973. console.log("\n\nSkipping tests with " + id_type + " '" + id + "' due to case-insensitive rate-limiting\n\n");
  974. }
  975. cache.get_redis().flushall();
  976. });
  977. if (id_type === "uuid") {
  978. // just making sure ...
  979. it("uuid SHOULD be rate limited", function(done) {
  980. networking.get_profile(rid(), id, function() {
  981. networking.get_profile(rid(), id, function(err, profile) {
  982. assert.strictEqual(err.toString(), "HTTP: 429");
  983. assert.strictEqual(profile, null);
  984. done();
  985. });
  986. });
  987. });
  988. } else {
  989. it("username should NOT be rate limited (username)", function(done) {
  990. helpers.get_avatar(rid(), id, false, 160, function() {
  991. helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
  992. assert.strictEqual(err, null);
  993. done();
  994. });
  995. });
  996. });
  997. }
  998. });
  999. }(i, iid, iid_type));
  1000. }
  1001. });