From d2f122936ee0a94e0b9326658b1969188787fb19 Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Sat, 17 Jun 2017 17:24:24 +0200 Subject: [PATCH] Tests for handleInventory(), 93% avg coverage --- package-lock.json | 56 ++++++++ package.json | 4 +- sharedPlugins/inventoryImports/index.js | 2 +- .../inventoryImports/interfacingFunctions.js | 13 +- .../interfacingFunctions.spec.js | 122 ++++++++++++++---- 5 files changed, 163 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d3a24e0..dfef3f22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -549,6 +549,12 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" }, + "formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "dev": true + }, "forwarded": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", @@ -941,6 +947,12 @@ "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "dev": true }, + "lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", + "dev": true + }, "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -1041,6 +1053,12 @@ } } }, + "mocha-sinon": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mocha-sinon/-/mocha-sinon-2.0.0.tgz", + "integrity": "sha1-cjqTEOfXN9e3fHpmghI3QlsDLUg=", + "dev": true + }, "mock-fs": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.4.1.tgz", @@ -1052,6 +1070,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "dev": true + }, "ncp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", @@ -1290,6 +1314,12 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.0.tgz", "integrity": "sha512-aSLEDudu6OoRr/2rU609gRmnYboRLxgDG1z9o2Q0os7236FwvcqIOO8r8U5JUEwivZOhDaKlFO4SbPTJYyBEyQ==" }, + "samsam": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.2.1.tgz", + "integrity": "sha1-7dOQk6MYQ3DLhZJDsr3yVefY6mc=", + "dev": true + }, "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", @@ -1315,6 +1345,26 @@ "resolved": "https://registry.npmjs.org/simple-rcon/-/simple-rcon-0.3.1.tgz", "integrity": "sha1-IwPbzDE5yJPscApyeLukP4yxBDs=" }, + "sinon": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.3.4.tgz", + "integrity": "sha1-RmrY0brobW21GqIYuS6Ze8Pl24g=", + "dev": true, + "dependencies": { + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true + }, + "type-detect": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.3.tgz", + "integrity": "sha1-Dj8mcLRAmbC0bChNE2p+9Jx0wuo=", + "dev": true + } + } + }, "sntp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", @@ -1378,6 +1428,12 @@ "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-4.1.0.tgz", "integrity": "sha512-iFbOBWYaznBNbheIKaMkj+3EabpEsXbuwcTVuYkRjoav+Om5L8VXXLIXms0cHxkouXMRCQaSfhfau9/HyIbM2Q==" }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, "then-request": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/then-request/-/then-request-2.2.0.tgz", diff --git a/package.json b/package.json index 6bf26bdb..e4a96cf8 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,9 @@ "chai": "^3.5.0", "istanbul": "^0.4.5", "mocha": "^3.4.1", + "mocha-sinon": "^2.0.0", "mock-fs": "^4.3.0", - "nock": "^9.0.13" + "nock": "^9.0.13", + "sinon": "^2.3.4" } } diff --git a/sharedPlugins/inventoryImports/index.js b/sharedPlugins/inventoryImports/index.js index a00f15c4..b90d568e 100644 --- a/sharedPlugins/inventoryImports/index.js +++ b/sharedPlugins/inventoryImports/index.js @@ -15,7 +15,7 @@ process.stdin.on('readable', () => { let inventory = JSON.parse(chunk) // process inventory of players and ask master for more stuff - functions.handleInventory(inventory, config); + functions.handleInventory(inventory, config, console.log); } else if(isJson(chunk) && JSON.parse(chunk).factorioPort){ config = JSON.parse(chunk); } diff --git a/sharedPlugins/inventoryImports/interfacingFunctions.js b/sharedPlugins/inventoryImports/interfacingFunctions.js index 0e24a6f9..60898b98 100644 --- a/sharedPlugins/inventoryImports/interfacingFunctions.js +++ b/sharedPlugins/inventoryImports/interfacingFunctions.js @@ -1,6 +1,7 @@ const objectOps = require("../../lib/objectOps.js"); const needle = require("needle"); const pluginConfig = require("./config") +const isJson = require("./isJson") // returns JS object, throws if there are syntax errors in input string function parseJsString(string){ @@ -18,7 +19,7 @@ function parseJsString(string){ console.log(inventory)*/ return inventory; } -function handleInventory(json, config){ +function handleInventory(json, config, callback){ /* json is one of these two samples {"players":{"1":{"inventory":{"stone":12,"iron-ore":120,"raw-wood":10,"iron-plate":8,"steel-plate":480},"requestSlots":{}}}} @@ -48,6 +49,8 @@ function handleInventory(json, config){ let collectStacks = function(stack){ itemsToConfirm--; if(stack){ + // because of the way we collect data in a keyed JSON object, this first part of the IF cannot happen. + /* istanbul ignore next */ if(confirmedItems[stack.name] && confirmedItems[stack.name] > 0){ confirmedItems[stack.name] += stack.count; } else { @@ -55,7 +58,8 @@ function handleInventory(json, config){ } } if(itemsToConfirm == 0 && Object.keys(confirmedItems).length > 0){ - console.log(insertItemsFromObject(confirmedItems, playerNames[i])); + // return LUA + callback(insertItemsFromObject(confirmedItems, playerNames[i])); } } // subtract items already in inventory @@ -72,10 +76,11 @@ function handleInventory(json, config){ } //console.log("Requesting: "+JSON.stringify(request)); // request the difference from master - needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', request, function (err, response, body) { - if (response && response.body && typeof response.body == "object") { + needle.post(config.masterIP + ":" + config.masterPort + '/api/remove', request, {json:true}, function (err, response, body) { + if (!err && response && response.body && (typeof response.body == "object" || isJson(response.body))) { // confirmed orders, already removed from master inventory //response.body.name: response.body.count + if(typeof response.body == "string") response.body = JSON.parse(response.body); let stack = {name:response.body.name,count:Number(response.body.count)} collectStacks(stack); } else { diff --git a/sharedPlugins/inventoryImports/interfacingFunctions.spec.js b/sharedPlugins/inventoryImports/interfacingFunctions.spec.js index d6f24c2f..90411831 100644 --- a/sharedPlugins/inventoryImports/interfacingFunctions.spec.js +++ b/sharedPlugins/inventoryImports/interfacingFunctions.spec.js @@ -2,45 +2,111 @@ const assert = require("assert"); const child_process = require("child_process"); const functions = require("./interfacingFunctions"); const luamin = require("luamin"); -const isFactorioCommand = require("./isFactorioCommand") +const isFactorioCommand = require("./isFactorioCommand"); +const nock = require("nock"); + +// sinon does weird prototype manipulation and so doen't need to be saved to anything. +require("mocha-sinon"); +const sinon = require("sinon"); describe("inventoryImports/interfacingFunctions.js", function(){ describe("handleInventory(inventory, config)", function(){ - /*it("does things to an object", function(){ - let inventory = {'1':{inventory: {"iron-plate":63,'raw-wood': 4},requestSlots:{'raw-wood': 50,'fast-transport-belt': 50,'express-underground-belt': 50}}}; - assert.throws(function(){ - functions.handleInventory(inventory); - }, function(err){ - // convert error to string - let error = ""+err - // expected error is here - /* istanbul ignore else */ - /*if(error.includes("TypeError: Cannot read property 'masterIP' of undefined")) return true - }, "threw unexpected error"); - });*/ + let config = { + masterIP:"localhost", + masterPort:8080, + } it("returns false if given invalid input", function(){ assert(functions.handleInventory("this is a string") === false, "when given strings it should return false and do nothing"); }); - /*it("Sends post requests", function(){ + it("Sends post requests and outputs factorio commands", function(done){ + let inventory = { + "players":{ + "Danielv123":{ + "inventory":{ + "stone":12, + "iron-ore":120, + "raw-wood":10, + "iron-plate":8, + "steel-plate":480 + }, + "requestSlots":{ + "iron-plate":100, + "steel-plate":480, + "bullshit":0, + } + } + } + } + var scope = nock('http://localhost:8080') + .post('/api/remove', function(body){ + // check that we are requesting the right things, () to convert to boolean + // 92 because 100 requested and 8 already in inventory + if(body.name == "iron-plate") assert(body.count == "92"); + // nock wants us to return true if this is the correct body and we should reply + // if we return false we won't send a reply. + return x + }) + // nock destroys the endpoints after being hit once, this changes the number of hits required for destruction + .times(10) + // respond with JSON object instead of url query parameters + .reply(200,function(uri, req){ + return req + },{ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }) + // nock logging statement .log((data) => console.log(data)); - var scope = nock('localhost:8080') - .post('/api/remove', { - username: 'pgte', - email: 'pedro.teixeira@gmail.com' - }) - .reply(201, { - name: "copper-plate", - count: 100 - }); - - - });*/ + functions.handleInventory(inventory, config, callback); + function callback(x){ + assert(x.includes(92) && x.includes("iron-plate"), "it should have included the items it was going to import"); + assert(!x.includes("steel-plate"), "Inventory is already filled with steel so shouldn't be importing that"); + assert(isFactorioCommand(x), "it should have returned a valid factorio command"); + assert(x.includes('"Danielv123"'), "it should include the name of the player that will recieve the items"); + done(); + } + }); + it("posts leftovers to master", function(done){ + let inventory2 = { + "exports":{ + "1":[ + {"name":"iron-ore","count":1200}, + {"name":"steel-plate","count":1337}, + {"name":"copper-plate","count":0}, + ] + } + } + let timesRan = 0; + let callback = function(){ + timesRan++ + assert(timesRan <= 2, "placed more than 2 items, unexpeceted test result"); + if(timesRan == 2){ + done(); + } + } + var scope = nock('http://localhost:8080') + .post('/api/place', function(body){ + // check that we are requesting the right things + assert(body.name != "copper-plate", "copper plate should not be placed because it is 0"); + assert(body.count > 0, "no need for placing 0 things"); + assert(Number(body.count) != NaN, "count should be a number"); + callback(); + return true; + }) + // nock destroys the endpoints after being hit once, this changes the number of hits required for destruction + .times(10) + .reply(200,"affirmative") + // if it tries using the nonexisting callback (argv 3) it will throw. It shoudln't. + assert.doesNotThrow(function(){ + functions.handleInventory(inventory2, config); + }, "this here threw for some reason, remove assert.doesNotThrow for easier debugging of this issue"); + }); }); describe("pollInventories(outputfile)", function(){ it("Returns a big factorio command as string", function(){ let x = functions.pollInventories("output.txt"); assert(typeof x == "string", "pollInventories() should always return a string"); - assert(x.includes("/silent-command ")||x.includes("/c "), "This is not a command factorio will accept, please prepend /c"); + assert(isFactorioCommand(x), "This is not a command factorio will accept, please prepend /c"); assert(x.length > 20, "LUA string seems to short, this ain't good"); }); it("Returns valid LUA", function(){ @@ -81,7 +147,7 @@ describe("inventoryImports/interfacingFunctions.js", function(){ assert(typeof x == "string", "LUA returned should be a string"); assert(x.length > 20, "LUA strings are quite long"); - assert(x.includes("/silent-command ")||x.includes("/c "), "This is not a command factorio will accept, please prepend /c"); + assert(isFactorioCommand(x), "This is not a command factorio will accept, please prepend /c"); }); it("The returned string is a valid factorio command", function(){ let x = functions.insertItemsFromObject(testObject, playerName);