From af59febbb55b8c27cec6d1be4a90eadfbe46c880 Mon Sep 17 00:00:00 2001 From: MillenniumEarl Date: Mon, 2 Nov 2020 15:06:09 +0100 Subject: [PATCH] Added unit tests --- test/user-test.js => app/example.js | 2 +- app/index.js | 4 +- app/scripts/classes/game-info.js | 9 +- app/scripts/network-helper.js | 36 ++-- app/scripts/shared.js | 1 + package-lock.json | 2 +- package.json | 7 +- test/index-test-legacy.js | 290 ++++++++++++++++++++++++++ test/index-test.js | 302 +++------------------------- test/suites/api-test.js | 64 ++++++ test/suites/credentials-test.js | 52 +++++ test/suites/network-helper-test.js | 107 ++++++++++ test/suites/scraper-test.js | 42 ++++ test/suites/searcher-test.js | 64 ++++++ test/suites/user-scraper-test.js | 54 +++++ 15 files changed, 738 insertions(+), 298 deletions(-) rename test/user-test.js => app/example.js (96%) create mode 100644 test/index-test-legacy.js create mode 100644 test/suites/api-test.js create mode 100644 test/suites/credentials-test.js create mode 100644 test/suites/network-helper-test.js create mode 100644 test/suites/scraper-test.js create mode 100644 test/suites/searcher-test.js create mode 100644 test/suites/user-scraper-test.js diff --git a/test/user-test.js b/app/example.js similarity index 96% rename from test/user-test.js rename to app/example.js index 804fa2d..286425e 100644 --- a/test/user-test.js +++ b/app/example.js @@ -4,7 +4,7 @@ const dotenv = require("dotenv"); // Modules from file -const F95API = require("../app/index.js"); +const F95API = require("./index.js"); // Configure the .env reader dotenv.config(); diff --git a/app/index.js b/app/index.js index 4ceb06e..3bff9ce 100644 --- a/app/index.js +++ b/app/index.js @@ -24,6 +24,7 @@ module.exports.UserData = UserData; * Shows log messages and other useful functions for module debugging. * @param {Boolean} value */ +/* istambul ignore next */ module.exports.debug = function (value) { shared.debug = value; @@ -35,6 +36,7 @@ module.exports.debug = function (value) { * Indicates whether a user is logged in to the F95Zone platform or not. * @returns {String} */ +/* istambul ignore next */ module.exports.isLogged = function () { return shared.isLogged; }; @@ -64,7 +66,7 @@ module.exports.login = async function (username, password) { await creds.fetchToken(); shared.logger.trace(`Authentication for ${username}`); - const result = await networkHelper.autenticate(creds); + const result = await networkHelper.authenticate(creds); shared.isLogged = result.success; if (result.success) shared.logger.info("User logged in through the platform"); diff --git a/app/scripts/classes/game-info.js b/app/scripts/classes/game-info.js index 9d9e564..84a0aac 100644 --- a/app/scripts/classes/game-info.js +++ b/app/scripts/classes/game-info.js @@ -107,6 +107,7 @@ class GameInfo { censored: this.censored, engine: this.engine, status: this.status, + tags: this.tags, previewSrc: this.previewSrc, version: this.version, lastUpdate: this.lastUpdate, @@ -122,9 +123,13 @@ class GameInfo { * @param {String} json JSON string used to create the new object * @returns {GameInfo} */ - /* istanbul ignore next */ static fromJSON(json) { - return Object.assign(new GameInfo(), json); + // Convert string + const temp = Object.assign(new GameInfo(), JSON.parse(json)); + + // JSON cannot transform a string to a date implicitly + temp.lastUpdate = new Date(temp.lastUpdate); + return temp; } } module.exports = GameInfo; diff --git a/app/scripts/network-helper.js b/app/scripts/network-helper.js index 8c34bc9..7e3ae4a 100644 --- a/app/scripts/network-helper.js +++ b/app/scripts/network-helper.js @@ -2,7 +2,6 @@ // Public modules from npm const axios = require("axios").default; -const { isString } = require("lodash"); const ky = require("ky-universal").create({ throwHttpErrors: false, }); @@ -21,6 +20,7 @@ const userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) " + "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15"; axiosCookieJarSupport(axios); const cookieJar = new tough.CookieJar(); + const commonConfig = { headers: { "User-Agent": userAgent, @@ -52,9 +52,10 @@ module.exports.fetchHTML = async function (url) { * and token obtained previously. Save cookies on your * device after authentication. * @param {Credentials} credentials Platform access credentials - * @returns {Promise} Result of the operation + * @param {Boolea} force Specifies whether the request should be forced, ignoring any saved cookies + * @returns {Promise} Result of the operation */ -module.exports.autenticate = async function (credentials) { +module.exports.authenticate = async function (credentials, force) { shared.logger.info(`Authenticating with user ${credentials.username}`); if (!credentials.token) throw new Error(`Invalid token for auth: ${credentials.token}`); @@ -75,7 +76,9 @@ module.exports.autenticate = async function (credentials) { try { // Try to log-in - const response = await axios.post(secureURL, params, commonConfig); + let config = Object.assign({}, commonConfig); + if (force) delete config.jar; + const response = await axios.post(secureURL, params, config); // Parse the response HTML const $ = cheerio.load(response.data); @@ -116,6 +119,7 @@ module.exports.getF95Token = async function() { * (such as graphics engines and progress statuses) * @deprecated */ +/* istanbul ignore next */ module.exports.fetchPlatformData = async function() { // Fetch the response of the platform const response = await exports.fetchGETResponse(f95url.F95_LATEST_UPDATES); @@ -145,6 +149,12 @@ module.exports.fetchPlatformData = async function() { }; //#region Utility methods +/** + * @protected + * Performs a GET request to a specific URL and returns the response. + * If the request generates an error (for example 400) `null` is returned. + * @param {String} url + */ module.exports.fetchGETResponse = async function(url) { // Secure the URL const secureURL = exports.enforceHttpsUrl(url); @@ -162,10 +172,10 @@ module.exports.fetchGETResponse = async function(url) { * @protected * Enforces the scheme of the URL is https and returns the new URL. * @param {String} url - * @returns {String} + * @returns {String} Secure URL or `null` if the argument is not a string */ module.exports.enforceHttpsUrl = function (url) { - return isString(url) ? url.replace(/^(https?:)?\/\//, "https://") : null; + return exports.isStringAValidURL(url) ? url.replace(/^(https?:)?\/\//, "https://") : null; }; /** @@ -181,17 +191,17 @@ module.exports.isF95URL = function (url) { /** * @protected - * Checks if the string passed by parameter has a properly formatted and valid path to a URL. + * Checks if the string passed by parameter has a + * properly formatted and valid path to a URL (HTTP/HTTPS). * @param {String} url String to check for correctness * @returns {Boolean} true if the string is a valid URL, false otherwise */ module.exports.isStringAValidURL = function (url) { - try { - new URL(url); // skipcq: JS-0078 - return true; - } catch (err) { - return false; - } + // Many thanks to Daveo at StackOverflow (https://preview.tinyurl.com/y2f2e2pc) + const expression = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/; + const regex = new RegExp(expression); + if (url.match(regex)) return true; + else return false; }; /** diff --git a/app/scripts/shared.js b/app/scripts/shared.js index 561441e..f04d700 100644 --- a/app/scripts/shared.js +++ b/app/scripts/shared.js @@ -1,3 +1,4 @@ +/* istanbul ignore file */ "use strict"; // Public modules from npm diff --git a/package-lock.json b/package-lock.json index 26492f3..9f2c127 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "f95api", - "version": "1.3.5", + "version": "1.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2fb3213..30e5b5d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "main": "./app/index.js", "name": "f95api", - "version": "1.3.5", + "version": "1.5.0", "author": { "name": "Millennium Earl" }, @@ -19,7 +19,10 @@ "scraping", "login", "game", - "games" + "games", + "data", + "userdata", + "user data" ], "scripts": { "unit-test-mocha": "nyc --reporter=text mocha './test/index-test.js'", diff --git a/test/index-test-legacy.js b/test/index-test-legacy.js new file mode 100644 index 0000000..f2d9748 --- /dev/null +++ b/test/index-test-legacy.js @@ -0,0 +1,290 @@ +"use strict"; + +// Core modules +const fs = require("fs"); + +// Public modules from npm +const _ = require("lodash"); +const expect = require("chai").expect; +const sleep = require("sleep"); +const dotenv = require("dotenv"); + +// Modules from file +const urlHelper = require("../app/scripts/url-helper.js"); +const F95API = require("../app/index.js"); + +// Configure the .env reader +dotenv.config(); + +const COOKIES_SAVE_PATH = "./f95cache/cookies.json"; +const ENGINES_SAVE_PATH = "./f95cache/engines.json"; +const STATUSES_SAVE_PATH = "./f95cache/statuses.json"; +const USERNAME = process.env.F95_USERNAME; +const PASSWORD = process.env.F95_PASSWORD; +const FAKE_USERNAME = "Fake_Username091276"; +const FAKE_PASSWORD = "fake_password"; + +//F95API.debug(false); + +function randomSleep() { + const random = Math.floor(Math.random() * 500) + 50; + sleep.msleep(500 + random); +} + +describe("Login without cookies", function () { + //#region Set-up + this.timeout(30000); // All tests in this suite get 30 seconds before timeout + + before("Set isolation", function () { + F95API.setIsolation(true); + }); + + beforeEach("Remove all cookies", function () { + // Runs before each test in this block + if (fs.existsSync(COOKIES_SAVE_PATH)) fs.unlinkSync(COOKIES_SAVE_PATH); + if (F95API.isLogged()) F95API.logout(); + }); + //#endregion Set-up + + let testOrder = 0; + + it("Test with valid credentials", async function () { + // Gain exclusive use of the cookies + while (testOrder !== 0) randomSleep(); + + const result = await F95API.login(USERNAME, PASSWORD); + expect(result.success).to.be.true; + expect(result.message).equal("Authentication successful"); + + testOrder = 1; + }); + it("Test with invalid username", async function () { + // Gain exclusive use of the cookies + while (testOrder !== 1) randomSleep(); + + const result = await F95API.login(FAKE_USERNAME, FAKE_PASSWORD); + expect(result.success).to.be.false; + expect(result.message).to.equal("Incorrect username"); + + testOrder = 2; + }); + it("Test with invalid password", async function () { + // Gain exclusive use of the cookies + while (testOrder !== 2) randomSleep(); + + const result = await F95API.login(USERNAME, FAKE_PASSWORD); + expect(result.success).to.be.false; + expect(result.message).to.equal("Incorrect password"); + + testOrder = 3; + }); + it("Test with invalid credentials", async function () { + // Gain exclusive use of the cookies + while (testOrder !== 3) randomSleep(); + + const result = await F95API.login(FAKE_USERNAME, FAKE_PASSWORD); + expect(result.success).to.be.false; + expect(result.message).to.equal("Incorrect username"); // It should first check the username + + testOrder = 4; + }); +}); + +describe("Login with cookies", function () { + //#region Set-up + this.timeout(30000); // All tests in this suite get 30 seconds before timeout + + before("Log in to create cookies then logout", async function () { + // Runs once before the first test in this block + if (!fs.existsSync(COOKIES_SAVE_PATH)) + await F95API.login(USERNAME, PASSWORD); // Download cookies + if (F95API.isLogged()) F95API.logout(); + }); + //#endregion Set-up + + it("Test with valid credentials", async function () { + const result = await F95API.login(USERNAME, PASSWORD); + expect(result.success).to.be.true; + expect(result.message).equal("Logged with cookies"); + }); +}); + +describe("Load base data without cookies", function () { + //#region Set-up + this.timeout(30000); // All tests in this suite get 30 seconds before timeout + + before("Delete cache if exists", function () { + // Runs once before the first test in this block + if (fs.existsSync(ENGINES_SAVE_PATH)) fs.unlinkSync(ENGINES_SAVE_PATH); + if (fs.existsSync(STATUSES_SAVE_PATH)) fs.unlinkSync(STATUSES_SAVE_PATH); + }); + //#endregion Set-up + + it("With login", async function () { + const loginResult = await F95API.login(USERNAME, PASSWORD); + expect(loginResult.success).to.be.true; + + const result = await F95API.loadF95BaseData(); + + const enginesCacheExists = fs.existsSync(ENGINES_SAVE_PATH); + const statusesCacheExists = fs.existsSync(STATUSES_SAVE_PATH); + + expect(result).to.be.true; + expect(enginesCacheExists).to.be.true; + expect(statusesCacheExists).to.be.true; + }); + + it("Without login", async function () { + if (F95API.isLogged()) F95API.logout(); + const result = await F95API.loadF95BaseData(); + expect(result).to.be.false; + }); +}); + +describe("Search game data", function () { + //#region Set-up + this.timeout(60000); // All tests in this suite get 60 seconds before timeout + + beforeEach("Prepare API", function () { + // Runs once before the first test in this block + if (F95API.isLogged()) F95API.logout(); + }); + //#endregion Set-up + + let testGame = null; + + it("Search game when logged", async function () { + const loginResult = await F95API.login(USERNAME, PASSWORD); + expect(loginResult.success).to.be.true; + + const loadResult = await F95API.loadF95BaseData(); + expect(loadResult).to.be.true; + + // This test depend on the data on F95Zone at + // https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/ + const gamesList = await F95API.getGameData("Kingdom of Deception", false); + expect(gamesList.length, "Should find only the game").to.equal(1); + const result = gamesList[0]; + const src = "https://attachments.f95zone.to/2018/09/162821_f9nXfwF.png"; + + // Test only the main information + expect(result.name).to.equal("Kingdom of Deception"); + expect(result.author).to.equal("Hreinn Games"); + expect(result.isMod, "Should be false").to.be.false; + expect(result.engine).to.equal("REN'PY"); + expect(result.previewSource).to.equal(src); // Could be null -> Why sometimes doesn't get the image? + testGame = Object.assign({}, result); + }); + it("Search game when not logged", async function () { + const result = await F95API.getGameData("Kingdom of Deception", false); + expect(result, "Without being logged should return null").to.be.null; + }); + it("Test game serialization", function () { + const json = JSON.stringify(testGame); + const parsedGameInfo = JSON.parse(json); + const result = _.isEqual(parsedGameInfo, testGame); + expect(result).to.be.true; + }); +}); + +describe("Load user data", function () { + //#region Set-up + this.timeout(30000); // All tests in this suite get 30 seconds before timeout + //#endregion Set-up + + it("Retrieve when logged", async function () { + // Login + await F95API.login(USERNAME, PASSWORD); + + // Then retrieve user data + const data = await F95API.getUserData(); + + expect(data).to.exist; + expect(data.username).to.equal(USERNAME); + }); + it("Retrieve when not logged", async function () { + // Logout + if (F95API.isLogged()) F95API.logout(); + + // Try to retrieve user data + const data = await F95API.getUserData(); + + expect(data).to.be.null; + }); +}); + +describe("Check game update", function () { + //#region Set-up + this.timeout(30000); // All tests in this suite get 30 seconds before timeout + //#endregion Set-up + + it("Get online game and verify that no update exists", async function () { + const loginResult = await F95API.login(USERNAME, PASSWORD); + expect(loginResult.success).to.be.true; + + const loadResult = await F95API.loadF95BaseData(); + expect(loadResult).to.be.true; + + // This test depend on the data on F95Zone at + // https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/ + const result = (await F95API.getGameData("Kingdom of Deception", false))[0]; + + const update = await F95API.chekIfGameHasUpdate(result); + expect(update).to.be.false; + }); + + it("Verify that update exists from old URL", async function () { + const loginResult = await F95API.login(USERNAME, PASSWORD); + expect(loginResult.success).to.be.true; + + // This test depend on the data on F95Zone at + // https://f95zone.to/threads/perverted-education-v0-9701-april-ryan.1854/ + const url = + "https://f95zone.to/threads/perverted-education-v0-9701-april-ryan.1854/"; + const result = await F95API.getGameDataFromURL(url); + result.version = "0.9600"; + + const update = await F95API.chekIfGameHasUpdate(result); + expect(update).to.be.true; + }); +}); + +describe("Test url-helper", function () { + //#region Set-up + this.timeout(30000); // All tests in this suite get 30 seconds before timeout + //#endregion Set-up + + it("Check if URL exists", async function () { + // Check generic URLs... + let exists = await urlHelper.urlExists("https://www.google.com/"); + expect(exists, "Complete valid URL").to.be.true; + + exists = await urlHelper.urlExists("www.google.com"); + expect(exists, "URl without protocol prefix").to.be.false; + + exists = await urlHelper.urlExists("https://www.google/"); + expect(exists, "URL without third level domain").to.be.false; + + // Now check for more specific URLs (with redirect)... + exists = await urlHelper.urlExists( + "https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/" + ); + expect(exists, "URL with redirect without check").to.be.true; + + exists = await urlHelper.urlExists( + "https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/", + true + ); + expect(exists, "URL with redirect with check").to.be.false; + }); + + it("Check if URL belong to the platform", async function () { + let belong = urlHelper.isF95URL( + "https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/" + ); + expect(belong).to.be.true; + + belong = urlHelper.isF95URL("https://www.google/"); + expect(belong).to.be.false; + }); +}); diff --git a/test/index-test.js b/test/index-test.js index 7b69d4c..069c9bc 100644 --- a/test/index-test.js +++ b/test/index-test.js @@ -1,290 +1,36 @@ "use strict"; -// Core modules -const fs = require("fs"); +// Test suite +const api = require("./suites/api-test.js").suite; +const credentials = require("./suites/credentials-test.js").suite; +const network = require("./suites/network-helper-test.js").suite; +const scraper = require("./suites/scraper-test.js").suite; +const searcher = require("./suites/searcher-test.js").suite; +const uScraper = require("./suites/user-scraper-test.js").suite; -// Public modules from npm -const _ = require("lodash"); -const expect = require("chai").expect; -const sleep = require("sleep"); -const dotenv = require("dotenv"); - -// Modules from file -const urlHelper = require("../app/scripts/url-helper.js"); -const F95API = require("../app/index.js"); - -// Configure the .env reader -dotenv.config(); - -const COOKIES_SAVE_PATH = "./f95cache/cookies.json"; -const ENGINES_SAVE_PATH = "./f95cache/engines.json"; -const STATUSES_SAVE_PATH = "./f95cache/statuses.json"; -const USERNAME = process.env.F95_USERNAME; -const PASSWORD = process.env.F95_PASSWORD; -const FAKE_USERNAME = "FakeUsername091276"; -const FAKE_PASSWORD = "fake_password"; - -//F95API.debug(false); - -function randomSleep() { - const random = Math.floor(Math.random() * 500) + 50; - sleep.msleep(500 + random); -} - -describe("Login without cookies", function () { +describe("Test basic function", function testBasic() { //#region Set-up - this.timeout(30000); // All tests in this suite get 30 seconds before timeout - - before("Set isolation", function () { - F95API.setIsolation(true); - }); - - beforeEach("Remove all cookies", function () { - // Runs before each test in this block - if (fs.existsSync(COOKIES_SAVE_PATH)) fs.unlinkSync(COOKIES_SAVE_PATH); - if (F95API.isLogged()) F95API.logout(); - }); + this.timeout(15000); // All tests in this suite get 15 seconds before timeout //#endregion Set-up - let testOrder = 0; - - it("Test with valid credentials", async function () { - // Gain exclusive use of the cookies - while (testOrder !== 0) randomSleep(); - - const result = await F95API.login(USERNAME, PASSWORD); - expect(result.success).to.be.true; - expect(result.message).equal("Authentication successful"); - - testOrder = 1; - }); - it("Test with invalid username", async function () { - // Gain exclusive use of the cookies - while (testOrder !== 1) randomSleep(); - - const result = await F95API.login(FAKE_USERNAME, FAKE_PASSWORD); - expect(result.success).to.be.false; - expect(result.message).to.equal("Incorrect username"); - - testOrder = 2; - }); - it("Test with invalid password", async function () { - // Gain exclusive use of the cookies - while (testOrder !== 2) randomSleep(); - - const result = await F95API.login(USERNAME, FAKE_PASSWORD); - expect(result.success).to.be.false; - expect(result.message).to.equal("Incorrect password"); - - testOrder = 3; - }); - it("Test with invalid credentials", async function () { - // Gain exclusive use of the cookies - while (testOrder !== 3) randomSleep(); - - const result = await F95API.login(FAKE_USERNAME, FAKE_PASSWORD); - expect(result.success).to.be.false; - expect(result.message).to.equal("Incorrect username"); // It should first check the username - - testOrder = 4; - }); + describe("Test credentials class", credentials.bind(this)); + describe("Test network helper", network.bind(this)); }); -describe("Login with cookies", function () { +describe("Test F95 modules", function testF95Modules() { //#region Set-up - this.timeout(30000); // All tests in this suite get 30 seconds before timeout + this.timeout(15000); // All tests in this suite get 15 seconds before timeout + //#endregion Set-up + + describe("Test scraper methods", scraper.bind(this)); + describe("Test searcher methods", searcher.bind(this)); + describe("Test user scraper methods", uScraper.bind(this)); +}); - before("Log in to create cookies then logout", async function () { - // Runs once before the first test in this block - if (!fs.existsSync(COOKIES_SAVE_PATH)) - await F95API.login(USERNAME, PASSWORD); // Download cookies - if (F95API.isLogged()) F95API.logout(); - }); +describe("Test complete API", function testAPI() { + //#region Set-up + this.timeout(15000); // All tests in this suite get 15 seconds before timeout //#endregion Set-up - it("Test with valid credentials", async function () { - const result = await F95API.login(USERNAME, PASSWORD); - expect(result.success).to.be.true; - expect(result.message).equal("Logged with cookies"); - }); -}); - -describe("Load base data without cookies", function () { - //#region Set-up - this.timeout(30000); // All tests in this suite get 30 seconds before timeout - - before("Delete cache if exists", function () { - // Runs once before the first test in this block - if (fs.existsSync(ENGINES_SAVE_PATH)) fs.unlinkSync(ENGINES_SAVE_PATH); - if (fs.existsSync(STATUSES_SAVE_PATH)) fs.unlinkSync(STATUSES_SAVE_PATH); - }); - //#endregion Set-up - - it("With login", async function () { - const loginResult = await F95API.login(USERNAME, PASSWORD); - expect(loginResult.success).to.be.true; - - const result = await F95API.loadF95BaseData(); - - const enginesCacheExists = fs.existsSync(ENGINES_SAVE_PATH); - const statusesCacheExists = fs.existsSync(STATUSES_SAVE_PATH); - - expect(result).to.be.true; - expect(enginesCacheExists).to.be.true; - expect(statusesCacheExists).to.be.true; - }); - - it("Without login", async function () { - if (F95API.isLogged()) F95API.logout(); - const result = await F95API.loadF95BaseData(); - expect(result).to.be.false; - }); -}); - -describe("Search game data", function () { - //#region Set-up - this.timeout(60000); // All tests in this suite get 60 seconds before timeout - - beforeEach("Prepare API", function () { - // Runs once before the first test in this block - if (F95API.isLogged()) F95API.logout(); - }); - //#endregion Set-up - - let testGame = null; - - it("Search game when logged", async function () { - const loginResult = await F95API.login(USERNAME, PASSWORD); - expect(loginResult.success).to.be.true; - - const loadResult = await F95API.loadF95BaseData(); - expect(loadResult).to.be.true; - - // This test depend on the data on F95Zone at - // https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/ - const gamesList = await F95API.getGameData("Kingdom of Deception", false); - expect(gamesList.length, "Should find only the game").to.equal(1); - const result = gamesList[0]; - const src = "https://attachments.f95zone.to/2018/09/162821_f9nXfwF.png"; - - // Test only the main information - expect(result.name).to.equal("Kingdom of Deception"); - expect(result.author).to.equal("Hreinn Games"); - expect(result.isMod, "Should be false").to.be.false; - expect(result.engine).to.equal("REN'PY"); - expect(result.previewSource).to.equal(src); // Could be null -> Why sometimes doesn't get the image? - testGame = Object.assign({}, result); - }); - it("Search game when not logged", async function () { - const result = await F95API.getGameData("Kingdom of Deception", false); - expect(result, "Without being logged should return null").to.be.null; - }); - it("Test game serialization", function () { - const json = JSON.stringify(testGame); - const parsedGameInfo = JSON.parse(json); - const result = _.isEqual(parsedGameInfo, testGame); - expect(result).to.be.true; - }); -}); - -describe("Load user data", function () { - //#region Set-up - this.timeout(30000); // All tests in this suite get 30 seconds before timeout - //#endregion Set-up - - it("Retrieve when logged", async function () { - // Login - await F95API.login(USERNAME, PASSWORD); - - // Then retrieve user data - const data = await F95API.getUserData(); - - expect(data).to.exist; - expect(data.username).to.equal(USERNAME); - }); - it("Retrieve when not logged", async function () { - // Logout - if (F95API.isLogged()) F95API.logout(); - - // Try to retrieve user data - const data = await F95API.getUserData(); - - expect(data).to.be.null; - }); -}); - -describe("Check game update", function () { - //#region Set-up - this.timeout(30000); // All tests in this suite get 30 seconds before timeout - //#endregion Set-up - - it("Get online game and verify that no update exists", async function () { - const loginResult = await F95API.login(USERNAME, PASSWORD); - expect(loginResult.success).to.be.true; - - const loadResult = await F95API.loadF95BaseData(); - expect(loadResult).to.be.true; - - // This test depend on the data on F95Zone at - // https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/ - const result = (await F95API.getGameData("Kingdom of Deception", false))[0]; - - const update = await F95API.chekIfGameHasUpdate(result); - expect(update).to.be.false; - }); - - it("Verify that update exists from old URL", async function () { - const loginResult = await F95API.login(USERNAME, PASSWORD); - expect(loginResult.success).to.be.true; - - // This test depend on the data on F95Zone at - // https://f95zone.to/threads/perverted-education-v0-9701-april-ryan.1854/ - const url = - "https://f95zone.to/threads/perverted-education-v0-9701-april-ryan.1854/"; - const result = await F95API.getGameDataFromURL(url); - result.version = "0.9600"; - - const update = await F95API.chekIfGameHasUpdate(result); - expect(update).to.be.true; - }); -}); - -describe("Test url-helper", function () { - //#region Set-up - this.timeout(30000); // All tests in this suite get 30 seconds before timeout - //#endregion Set-up - - it("Check if URL exists", async function () { - // Check generic URLs... - let exists = await urlHelper.urlExists("https://www.google.com/"); - expect(exists, "Complete valid URL").to.be.true; - - exists = await urlHelper.urlExists("www.google.com"); - expect(exists, "URl without protocol prefix").to.be.false; - - exists = await urlHelper.urlExists("https://www.google/"); - expect(exists, "URL without third level domain").to.be.false; - - // Now check for more specific URLs (with redirect)... - exists = await urlHelper.urlExists( - "https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/" - ); - expect(exists, "URL with redirect without check").to.be.true; - - exists = await urlHelper.urlExists( - "https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/", - true - ); - expect(exists, "URL with redirect with check").to.be.false; - }); - - it("Check if URL belong to the platform", async function () { - let belong = urlHelper.isF95URL( - "https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/" - ); - expect(belong).to.be.true; - - belong = urlHelper.isF95URL("https://www.google/"); - expect(belong).to.be.false; - }); -}); + describe("Test API", api.bind(this)); +}); \ No newline at end of file diff --git a/test/suites/api-test.js b/test/suites/api-test.js new file mode 100644 index 0000000..07e6016 --- /dev/null +++ b/test/suites/api-test.js @@ -0,0 +1,64 @@ +"use strict"; + +// Public module from npm +const expect = require("chai").expect; +const dotenv = require("dotenv"); +const { + isEqual +} = require("lodash"); + +// Modules from file +const F95API = require("../../app/index.js"); + +// Configure the .env reader +dotenv.config(); + +// Global variables +const USERNAME = process.env.F95_USERNAME; +const PASSWORD = process.env.F95_PASSWORD; + +module.exports.suite = function suite() { + // Global suite variables + const gameURL = "https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/"; + + it("Test login", async function testLogin() { + const result = await F95API.login(USERNAME, PASSWORD); + expect(result.success).to.be.true; + expect(F95API.isLogged()).to.be.true; + }); + + it("Test user data fetching", async function testUserDataFetch() { + const userdata = await F95API.getUserData(); + expect(userdata.username).to.be.equal(USERNAME); + }); + + it("Test game update checking", async function testGameUpdateCheck() { + // We force the creation of a GameInfo object, + // knowing that the checkIfGameHasUpdate() function + // only needs the game URL + const info = new F95API.GameInfo(); + + // The gameURL identifies a game for which we know there is an update + info.url = gameURL; + + // Check for updates + const update = await F95API.checkIfGameHasUpdate(info); + expect(update).to.be.true; + }); + + it("Test game data fetching", async function testGameDataFetch() { + // Search a game by name + const gameList = await F95API.getGameData("perverted education", false); + + // We know that there is only one game with the selected name + expect(gameList.length).to.be.equal(1, `There should be only one game, not ${gameList.length}`); + const game = gameList[0]; + + // Than we fetch a game from URL + const gameFromURL = await F95API.getGameDataFromURL(game.url); + + // The two games must be equal + const equal = isEqual(game, gameFromURL); + expect(equal).to.be.true; + }); +}; \ No newline at end of file diff --git a/test/suites/credentials-test.js b/test/suites/credentials-test.js new file mode 100644 index 0000000..3bf60e2 --- /dev/null +++ b/test/suites/credentials-test.js @@ -0,0 +1,52 @@ +"use strict"; + +// Public module from npm +const expect = require("chai").expect; + +// Modules from file +const Credentials = require("../../app/scripts/classes/credentials.js"); + +module.exports.suite = function suite() { + it("Check token formatting", async function testValidToken() { + // Token example: + // 1604309951,0338213c00fcbd894fd9415e6ba08403 + // 1604309986,ebdb75502337699381f0f55c86353555 + // 1604310008,2d50d55808e5ec3a157ec01953da9d26 + + // Fetch token (is a GET request, we don't need the credentials) + const cred = new Credentials(null, null); + await cred.fetchToken(); + + // Parse token for assert + const splitted = cred.token.split(","); + const unique = splitted[0]; + const hash = splitted[1]; + expect(splitted.length).to.be.equal(2, "The token consists of two parts"); + + // Check type of parts + expect(isNumeric(unique)).to.be.true; + expect(isNumeric(hash)).to.be.false; + + // The second part is most probably the MD5 hash of something + expect(hash.length).to.be.equal(32, "Hash should have 32 hex chars"); + }); +}; + +//#region Private methods +/** + * @private + * Check if a string is a number + * @param {String} str + * @author Dan, Ben Aston + * @see https://preview.tinyurl.com/y46jqwkt + */ +function isNumeric(str) { + // We only process strings! + if (typeof str != "string") return false; + + // Use type coercion to parse the _entirety_ of the string + // (`parseFloat` alone does not do this) and ensure strings + // of whitespace fail + return !isNaN(str) && !isNaN(parseFloat(str)); +} +//#endregion \ No newline at end of file diff --git a/test/suites/network-helper-test.js b/test/suites/network-helper-test.js new file mode 100644 index 0000000..a650c99 --- /dev/null +++ b/test/suites/network-helper-test.js @@ -0,0 +1,107 @@ +"use strict"; + +// Public module from npm +const expect = require("chai").expect; +const dotenv = require("dotenv"); + +// Modules from file +const Credentials = require("../../app/scripts/classes/credentials.js"); +const networkHelper = require("../../app/scripts/network-helper.js"); +const { + F95_SEARCH_URL +} = require("../../app/scripts/constants/url.js"); + +// Configure the .env reader +dotenv.config(); + +// Global variables +const USERNAME = process.env.F95_USERNAME; +const PASSWORD = process.env.F95_PASSWORD; +const FAKE_USERNAME = "Fake_Username091276"; +const FAKE_PASSWORD = "fake_password"; + +module.exports.suite = function suite() { + // Global suite variables + const gameURL = "https://f95zone.to/threads/perverted-education-v0-9601-april-ryan.1854/"; + + it("Check if URL exists", async function checkURLExistence() { + // Check generic URLs... + let exists = await networkHelper.urlExists("https://www.google.com/"); + expect(exists, "Complete valid URL").to.be.true; + + exists = await networkHelper.urlExists("www.google.com"); + expect(exists, "URl without protocol prefix").to.be.false; + + exists = await networkHelper.urlExists("https://www.google/"); + expect(exists, "URL without third level domain").to.be.false; + + // Now check for more specific URLs (with redirect)... + exists = await networkHelper.urlExists(gameURL); + expect(exists, "URL with redirect without check").to.be.true; + + exists = await networkHelper.urlExists(gameURL, true); + expect(exists, "URL with redirect with check").to.be.false; + }); + + it("Check if URL belong to the platform", function checkIfURLIsF95() { + let belong = networkHelper.isF95URL(gameURL); + expect(belong).to.be.true; + + belong = networkHelper.isF95URL("https://www.google/"); + expect(belong).to.be.false; + }); + + it("Enforce secure URLs", function testSecureURLEnforcement() { + // This URL is already secure, should remain the same + let enforced = networkHelper.enforceHttpsUrl(gameURL); + expect(enforced).to.be.equal(gameURL, "The game URL is already secure"); + + // This URL is not secure + enforced = networkHelper.enforceHttpsUrl("http://www.google.com"); + expect(enforced).to.be.equal("https://www.google.com", "The URL was without SSL/TLS (HTTPs)"); + + // Finally, we check when we pass a invalid URL + enforced = networkHelper.enforceHttpsUrl("http://invalidurl"); + expect(enforced).to.be.null; + }); + + it("Check URL redirect", async function checkURLRedirect() { + // gameURL is an old URL it has been verified that it generates a redirect + const redirectURL = await networkHelper.getUrlRedirect(gameURL); + expect(redirectURL).to.not.be.equal(gameURL, "The original URL has redirect"); + + // If we recheck the new URL, we find that no redirect happens + const secondRedirectURL = await networkHelper.getUrlRedirect(redirectURL); + expect(secondRedirectURL).to.be.equal(redirectURL, "The URL has no redirect"); + }); + + it("Check response to GET request", async function testGETResponse() { + // We should be able to fetch a game page + let response = await networkHelper.fetchGETResponse(gameURL); + expect(response.status).to.be.equal(200, "The operation must be successful"); + + // We should NOT be able to fetch the search page (we must be logged) + response = await networkHelper.fetchGETResponse(F95_SEARCH_URL); + expect(response).to.be.null; + }); + + it("Test for authentication to platform", async function testAuthentication() { + // Try to authenticate with valid credentials + const creds = new Credentials(USERNAME, PASSWORD); + await creds.fetchToken(); + const validResult = await networkHelper.authenticate(creds); + expect(validResult.success).to.be.true; + + // Now we use fake credentials + const fakeCreds = new Credentials(FAKE_USERNAME, FAKE_PASSWORD); + await fakeCreds.fetchToken(); + const invalidResult = await networkHelper.authenticate(fakeCreds, true); + expect(invalidResult.success).to.be.false; + }); + + it("Test fetching HTML", async function testFetchHTML() { + // This should return the HTML code of the page + const html = await networkHelper.fetchHTML(gameURL); + expect(html.startsWith("")).to.be.true; + }); +}; \ No newline at end of file diff --git a/test/suites/scraper-test.js b/test/suites/scraper-test.js new file mode 100644 index 0000000..a68666b --- /dev/null +++ b/test/suites/scraper-test.js @@ -0,0 +1,42 @@ +"use strict"; + +// Public module from npm +const expect = require("chai").expect; +const { isEqual } = require("lodash"); +const GameInfo = require("../../app/scripts/classes/game-info.js"); + +// Modules from file +const scraper = require("../../app/scripts/scraper.js"); + +module.exports.suite = function suite() { + // Global suite variables + const gameURL = "https://f95zone.to/threads/kingdom-of-deception-v0-10-8-hreinn-games.2733/"; + const previewSrc = "https://attachments.f95zone.to/2018/09/162821_f9nXfwF.png"; + + it("Search game", async function () { + // This test depend on the data on F95Zone at gameURL + const result = await scraper.getGameInfo(gameURL); + + // Test only the main information + expect(result.name).to.equal("Kingdom of Deception"); + expect(result.author).to.equal("Hreinn Games"); + expect(result.isMod, "Should be false").to.be.false; + expect(result.engine).to.equal("Ren'Py"); + expect(result.previewSrc).to.equal(previewSrc, "Preview not equals"); + }); + + it("Test game serialization", async function testGameSerialization() { + // This test depend on the data on F95Zone at gameURL + const testGame = await scraper.getGameInfo(gameURL); + + // Serialize... + const json = JSON.stringify(testGame); + + // Deserialize... + const parsedGameInfo = GameInfo.fromJSON(json); + + // Compare with lodash + const result = isEqual(parsedGameInfo, testGame); + expect(result).to.be.true; + }); +}; diff --git a/test/suites/searcher-test.js b/test/suites/searcher-test.js new file mode 100644 index 0000000..6b8aea4 --- /dev/null +++ b/test/suites/searcher-test.js @@ -0,0 +1,64 @@ +"use strict"; + +// Public module from npm +const expect = require("chai").expect; +const dotenv = require("dotenv"); + +// Modules from file +const Credentials = require("../../app/scripts/classes/credentials.js"); +const searcher = require("../../app/scripts/searcher.js"); +const { + authenticate +} = require("../../app/scripts/network-helper.js"); + +// Configure the .env reader +dotenv.config(); + +// Global variables +const USERNAME = process.env.F95_USERNAME; +const PASSWORD = process.env.F95_PASSWORD; + +module.exports.suite = function suite() { + // TODO: + // This method should delete the store F95Zone cookies, + // but what if the other tests require them? + + // it("Search game when not logged", async function searchGameWhenNotLogged() { + // // Search for a game that we know has only one result + // // but without logging in first + // const urls = await searcher.searchGame("kingdom of deception"); + // expect(urls.lenght).to.be.equal(0, "There should not be any URL"); + // }); + + it("Search game", async function searchGame() { + // Authenticate + const result = await auth(); + expect(result.success, "Authentication should be successful").to.be.true; + + // Search for a game that we know has only one result + const urls = await searcher.searchGame("kingdom of deception"); + expect(urls.length).to.be.equal(1, `There should be only one game result instead of ${urls.length}`); + }); + + it("Search mod", async function searchMod() { + // Authenticate + const result = await auth(); + expect(result.success, "Authentication should be successful").to.be.true; + + // Search for a mod that we know has only one result + const urls = await searcher.searchMod("kingdom of deception jdmod"); + expect(urls.length).to.be.equal(1, `There should be only one mod result instead of ${urls.length}`); + }); +}; + +//#region Private methods +/** + * @private + * Simple wrapper for authentication. + */ +async function auth() { + const creds = new Credentials(USERNAME, PASSWORD); + await creds.fetchToken(); + return await authenticate(creds); +} +//#endregion Private methods \ No newline at end of file diff --git a/test/suites/user-scraper-test.js b/test/suites/user-scraper-test.js new file mode 100644 index 0000000..e3abe0e --- /dev/null +++ b/test/suites/user-scraper-test.js @@ -0,0 +1,54 @@ +"use strict"; + +// Public module from npm +const expect = require("chai").expect; +const dotenv = require("dotenv"); + +// Modules from file +const Credentials = require("../../app/scripts/classes/credentials.js"); +const uScraper = require("../../app/scripts/user-scraper.js"); +const { + authenticate +} = require("../../app/scripts/network-helper.js"); + +// Configure the .env reader +dotenv.config(); + +// Global variables +const USERNAME = process.env.F95_USERNAME; +const PASSWORD = process.env.F95_PASSWORD; + +module.exports.suite = function suite() { + // TODO: + // This method should delete the store F95Zone cookies, + // but what if the other tests require them? + + // it("Fetch data when not logged", async function fetchUserDataWhenLogged() { + // const data = await uScraper.getUserData(); + // expect(data.username).to.be.equal(""); + // expect(data.avatarSrc).to.be.equal(""); + // expect(data.watchedThreads.length).to.be.equal(0); + // }); + + it("Fetch data when logged", async function fetchUserDataWhenNotLogged() { + // Authenticate + const result = await auth(); + expect(result.success, "Authentication should be successful").to.be.true; + + // We test only for the username, the other test data depends on the user logged + const data = await uScraper.getUserData(); + expect(data.username).to.be.equal(USERNAME); + }); +}; + +//#region Private methods +/** + * @private + * Simple wrapper for authentication. + */ +async function auth() { + const creds = new Credentials(USERNAME, PASSWORD); + await creds.fetchToken(); + return await authenticate(creds); +} +//#endregion Private methods \ No newline at end of file