From 93480c8b2936a0569f5fd4f5c9834ad234a0d144 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Fri, 4 Oct 2019 22:09:43 +0200 Subject: [PATCH] Refactor getFactorioLocale Move getFactorioLocale to the new lib/factorio namespace and refactor it to use async functions instead of callbacks. Split tests into integration with factorio and testing of the function itself. Also removes the cacheing from the function itself as this breaks the isolated testing of it. --- lib/factorio/index.js | 1 + lib/factorio/locale.js | 28 +++++++ lib/getFactorioLocale.js | 53 ------------- master.js | 17 +++-- .../factorio/data/base/locale/en/base.cfg | 4 + test/integration/locale.js | 62 ++++++++++++++++ test/lib/factorio/locale.js | 18 +++++ test/lib/getFactorioLocale.js | 74 ------------------- 8 files changed, 125 insertions(+), 132 deletions(-) create mode 100644 lib/factorio/locale.js delete mode 100644 lib/getFactorioLocale.js create mode 100644 test/file/factorio/data/base/locale/en/base.cfg create mode 100644 test/integration/locale.js create mode 100644 test/lib/factorio/locale.js delete mode 100644 test/lib/getFactorioLocale.js diff --git a/lib/factorio/index.js b/lib/factorio/index.js index f5f95b85..f682bbd2 100644 --- a/lib/factorio/index.js +++ b/lib/factorio/index.js @@ -1,3 +1,4 @@ module.exports = { + ...require('lib/factorio/locale'), ...require('lib/factorio/server'), }; diff --git a/lib/factorio/locale.js b/lib/factorio/locale.js new file mode 100644 index 00000000..36ee8a1e --- /dev/null +++ b/lib/factorio/locale.js @@ -0,0 +1,28 @@ +/** + * Function for parsing factorio locale from a local factorio install + */ + +"use strict"; +const fs = require("fs-extra"); +const path = require("path"); +const ini = require("ini"); + +/** + * Gets factorios locale as an object, does not respect mods. + * + * @param {string} factorioDirectory + * @param {string} languageCode + * @param {function} callback + */ +async function getLocale(dataDir, languageCode) { + if (typeof dataDir !== "string") throw new TypeError("dataDir must be a string"); + if (typeof languageCode !== "string") throw new TypeError("languageCode must be a string"); + + let localeFilePath = path.join(dataDir, "base", "locale", languageCode, "base.cfg"); + let content = await fs.readFile(localeFilePath, "utf8"); + return ini.parse(content); +} + +module.exports = { + getLocale, +}; diff --git a/lib/getFactorioLocale.js b/lib/getFactorioLocale.js deleted file mode 100644 index 72d84dd0..00000000 --- a/lib/getFactorioLocale.js +++ /dev/null @@ -1,53 +0,0 @@ -/** -Module for grabbing factorio locale from a local factorio install -@module getFactorioLocale -*/ - -const fs = require("fs"); -const ini = require("ini"); - -const cache = {}; - -/** -Gets raw factorio locale from the .ini file. -Not available outside of module through exports. - -@memberof getFactorioLocale -@inner -@param {string} factorioDirectory -@param {string} languageCode -@param {function} callback -*/ -function getLocale(factorioDirectory, languageCode, callback){ - var localeFile = factorioDirectory + '/data/base/locale/' + languageCode + '/base.cfg'; - fs.readFile(localeFile, "utf8", (err, rawLocale) => { - callback(err, rawLocale); - }); -} - -/** -Gets factorios locale as an object, does not respect mods. - -@param {string} factorioDirectory -@param {string} languageCode -@param {function} callback -*/ -module.exports.asObject = function getLocaleAsObject(factorioDirectory, languageCode, callback){ - if(typeof factorioDirectory != "string" || typeof languageCode != "string" || typeof callback != "function"){ - throw new Error("Error: wrong parameters provided"); - } - - if(!cache[languageCode]){ - getLocale(factorioDirectory, languageCode, (err, rawLocale) => { - if(err){ - callback(err); - } else { - let processedLocale = ini.parse(rawLocale); - cache[languageCode] = processedLocale; - callback(undefined, processedLocale); - } - }); - } else { - callback(undefined, cache[languageCode]); - } -} diff --git a/master.js b/master.js index e0a1986c..541752b7 100644 --- a/master.js +++ b/master.js @@ -32,9 +32,9 @@ let config = {}; // homebrew modules const generateSSLcert = require("lib/generateSSLcert"); -const getFactorioLocale = require("lib/getFactorioLocale"); const pluginManager = require("lib/manager/pluginManager.js"); const database = require("lib/database"); +const factorio = require("lib/factorio"); // homemade express middleware for token auth const authenticate = require("lib/authenticate"); @@ -670,19 +670,26 @@ app.post("/api/runCommand", authenticate.middleware, function(req,res) { res.status(400).send({auth: true, message: "Error: invalid request.body"}); } }); + +var localeCache; /** GET endpoint. Returns factorio's base locale as a JSON object. @memberof clusterioMaster @instance @alias api/getFactorioLocale -@returns {object{}} 2 deep nested object with base game factorio locale as key:value pairs +@returns {object} 2 deep nested object with base game factorio locale as key:value pairs */ app.get("/api/getFactorioLocale", function(req,res){ endpointHitCounter.labels(req.route.path).inc(); - getFactorioLocale.asObject(config.factorioDirectory, "en", (err, factorioLocale) => { - res.send(factorioLocale); - }); + if (!localeCache) { + factorio.getLocale(path.join(config.factorioDirectory, "data"), "en").then(locale => { + localeCache = locale; + res.send(localeCache); + }); + } else { + res.send(localeCache); + } }); /* Websockets for plugins */ diff --git a/test/file/factorio/data/base/locale/en/base.cfg b/test/file/factorio/data/base/locale/en/base.cfg new file mode 100644 index 00000000..d5ee03af --- /dev/null +++ b/test/file/factorio/data/base/locale/en/base.cfg @@ -0,0 +1,4 @@ +; Test data for getLocale +[test] +key-a=1 +key-b=2 diff --git a/test/integration/locale.js b/test/integration/locale.js new file mode 100644 index 00000000..8c42981e --- /dev/null +++ b/test/integration/locale.js @@ -0,0 +1,62 @@ +const assert = require("assert").strict; +const path = require("path"); + +const factorio = require("lib/factorio"); + + +describe("Integration of lib/factorio/locale", function() { + describe("getLocale()", function() { + let locale; + before(async function() { + locale = await factorio.getLocale(path.join("factorio", "data"), "en"); + }); + + it("returns a nested object with base game locale information", async function() { + assert.equal(typeof locale, "object"); + assert.equal(locale["entity-name"]["fish"], "Fish"); + assert.equal(locale["entity-name"]["small-lamp"], "Lamp"); + let empty_keys = 0; + Object.keys(locale).forEach(key => { + // first level of the nested object is always an object + assert.equal(typeof locale[key], "object"); + + Object.keys(locale[key]).forEach(key2 => { + // second level of the nested object is always a string, nearly always truthy + if (!locale[key][key2]) { + empty_keys++; + } + assert.equal(typeof locale[key][key2], "string"); + }); + + }); + assert(empty_keys < 10, `Got unusually high amount (${empty_keys}) of empty keys`); + }); + + it("has some duplicate keys", async function() { + let arrayOfKeys = []; + // collect all keys in a single array + Object.keys(locale).forEach(key => { + arrayOfKeys.push(key); + Object.keys(locale[key]).forEach(key2 => { + arrayOfKeys.push(key2); + }); + }); + + // check for duplicates + let duplicateKeys = []; + let i = 0; + arrayOfKeys.forEach(key => { + let o = 0; + arrayOfKeys.forEach(key2 => { + if(i != o && key == key2){ + duplicateKeys.push(key); + } + o++ + }); + i++; + }); + // we got 704 but this is a very unexact number + assert(duplicateKeys.length > 10) + }); + }); +}); diff --git a/test/lib/factorio/locale.js b/test/lib/factorio/locale.js new file mode 100644 index 00000000..f9322ba2 --- /dev/null +++ b/test/lib/factorio/locale.js @@ -0,0 +1,18 @@ +const assert = require("assert").strict; +const path = require("path"); + +const factorio = require("lib/factorio"); + + +describe("lib/factorio/locale", function() { + describe("getLocale()", function() { + it("verifies types of the arguments", async function() { + await assert.rejects(factorio.getLocale(1, "a"), new TypeError("dataDir must be a string")); + await assert.rejects(factorio.getLocale("a", 1), new TypeError("languageCode must be a string")); + }); + it("returns a nested object with locale definitions", async function() { + let locale = await factorio.getLocale(path.join("test", "file", "factorio", "data"), "en"); + assert.deepEqual(locale, { test: { "key-a": "1", "key-b": "2" } }); + }); + });; +}); diff --git a/test/lib/getFactorioLocale.js b/test/lib/getFactorioLocale.js deleted file mode 100644 index 043d32c7..00000000 --- a/test/lib/getFactorioLocale.js +++ /dev/null @@ -1,74 +0,0 @@ -const assert = require("assert"); -const util = require("util"); - -const getFactorioLocale = require("lib/getFactorioLocale"); -const asObjectAsync = util.promisify(getFactorioLocale.asObject); - -describe("getFactorioLocale.js", function() { - describe("asObject()", function() { - it("requires 2 strings and a function as parameters", function() { - function validate(err) { - return err.message == "Error: wrong parameters provided" - } - let message = "getFactorioLocale is responding incorrectly to invalid arguments" - let callback = (err,object) => true; - - assert.throws(() => getFactorioLocale.asObject(), validate, message); - assert.throws(() => getFactorioLocale.asObject("hello", "I forgot my callback"), validate, message); - assert.throws(() => getFactorioLocale.asObject(undefined, undefined, callback), validate, message); - - // the proper way to do it - getFactorioLocale.asObject("factorio_0.15.27", "en", callback); - }); - it("returns a nested object with base game locale information", async function() { - let object = await asObjectAsync("factorio", "en"); - - assert.equal(typeof object, "object"); - assert.equal(object["entity-name"]["fish"], "Fish"); - assert.equal(object["entity-name"]["small-lamp"], "Lamp"); - let empty_keys = 0; - Object.keys(object).forEach(key => { - // first level of the nested object is always an object - assert.equal(typeof object[key], "object"); - - Object.keys(object[key]).forEach(key2 => { - // second level of the nested object is always a string, nearly always truthy - if (!object[key][key2]) { - empty_keys++; - } - assert.equal(typeof object[key][key2], "string"); - }); - - }); - assert(empty_keys < 10, `Got unusually high amount (${empty_keys}) of empty keys`); - }); - it("has some duplicate keys", async function() { - let object = await asObjectAsync("factorio", "en"); - - let arrayOfKeys = []; - // collect all keys in a single array - Object.keys(object).forEach(key => { - arrayOfKeys.push(key); - Object.keys(object[key]).forEach(key2 => { - arrayOfKeys.push(key2); - }); - }); - - // check for duplicates - let duplicateKeys = []; - let i = 0; - arrayOfKeys.forEach(key => { - let o = 0; - arrayOfKeys.forEach(key2 => { - if(i != o && key == key2){ - duplicateKeys.push(key); - } - o++ - }); - i++; - }); - // we got 704 but this is a very unexact number - assert(duplicateKeys.length > 10) - }); - }); -}); \ No newline at end of file