Added unit tests

pull/44/head
MillenniumEarl 2020-11-02 15:06:09 +01:00
parent c88c9720a2
commit af59febbb5
15 changed files with 738 additions and 298 deletions

View File

@ -4,7 +4,7 @@
const dotenv = require("dotenv"); const dotenv = require("dotenv");
// Modules from file // Modules from file
const F95API = require("../app/index.js"); const F95API = require("./index.js");
// Configure the .env reader // Configure the .env reader
dotenv.config(); dotenv.config();

View File

@ -24,6 +24,7 @@ module.exports.UserData = UserData;
* Shows log messages and other useful functions for module debugging. * Shows log messages and other useful functions for module debugging.
* @param {Boolean} value * @param {Boolean} value
*/ */
/* istambul ignore next */
module.exports.debug = function (value) { module.exports.debug = function (value) {
shared.debug = 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. * Indicates whether a user is logged in to the F95Zone platform or not.
* @returns {String} * @returns {String}
*/ */
/* istambul ignore next */
module.exports.isLogged = function () { module.exports.isLogged = function () {
return shared.isLogged; return shared.isLogged;
}; };
@ -64,7 +66,7 @@ module.exports.login = async function (username, password) {
await creds.fetchToken(); await creds.fetchToken();
shared.logger.trace(`Authentication for ${username}`); shared.logger.trace(`Authentication for ${username}`);
const result = await networkHelper.autenticate(creds); const result = await networkHelper.authenticate(creds);
shared.isLogged = result.success; shared.isLogged = result.success;
if (result.success) shared.logger.info("User logged in through the platform"); if (result.success) shared.logger.info("User logged in through the platform");

View File

@ -107,6 +107,7 @@ class GameInfo {
censored: this.censored, censored: this.censored,
engine: this.engine, engine: this.engine,
status: this.status, status: this.status,
tags: this.tags,
previewSrc: this.previewSrc, previewSrc: this.previewSrc,
version: this.version, version: this.version,
lastUpdate: this.lastUpdate, lastUpdate: this.lastUpdate,
@ -122,9 +123,13 @@ class GameInfo {
* @param {String} json JSON string used to create the new object * @param {String} json JSON string used to create the new object
* @returns {GameInfo} * @returns {GameInfo}
*/ */
/* istanbul ignore next */
static fromJSON(json) { 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; module.exports = GameInfo;

View File

@ -2,7 +2,6 @@
// Public modules from npm // Public modules from npm
const axios = require("axios").default; const axios = require("axios").default;
const { isString } = require("lodash");
const ky = require("ky-universal").create({ const ky = require("ky-universal").create({
throwHttpErrors: false, 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"; "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0 Safari/605.1.15";
axiosCookieJarSupport(axios); axiosCookieJarSupport(axios);
const cookieJar = new tough.CookieJar(); const cookieJar = new tough.CookieJar();
const commonConfig = { const commonConfig = {
headers: { headers: {
"User-Agent": userAgent, "User-Agent": userAgent,
@ -52,9 +52,10 @@ module.exports.fetchHTML = async function (url) {
* and token obtained previously. Save cookies on your * and token obtained previously. Save cookies on your
* device after authentication. * device after authentication.
* @param {Credentials} credentials Platform access credentials * @param {Credentials} credentials Platform access credentials
* @returns {Promise<LoginResul>} Result of the operation * @param {Boolea} force Specifies whether the request should be forced, ignoring any saved cookies
* @returns {Promise<LoginResult>} 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}`); shared.logger.info(`Authenticating with user ${credentials.username}`);
if (!credentials.token) throw new Error(`Invalid token for auth: ${credentials.token}`); if (!credentials.token) throw new Error(`Invalid token for auth: ${credentials.token}`);
@ -75,7 +76,9 @@ module.exports.autenticate = async function (credentials) {
try { try {
// Try to log-in // 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 // Parse the response HTML
const $ = cheerio.load(response.data); const $ = cheerio.load(response.data);
@ -116,6 +119,7 @@ module.exports.getF95Token = async function() {
* (such as graphics engines and progress statuses) * (such as graphics engines and progress statuses)
* @deprecated * @deprecated
*/ */
/* istanbul ignore next */
module.exports.fetchPlatformData = async function() { module.exports.fetchPlatformData = async function() {
// Fetch the response of the platform // Fetch the response of the platform
const response = await exports.fetchGETResponse(f95url.F95_LATEST_UPDATES); const response = await exports.fetchGETResponse(f95url.F95_LATEST_UPDATES);
@ -145,6 +149,12 @@ module.exports.fetchPlatformData = async function() {
}; };
//#region Utility methods //#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) { module.exports.fetchGETResponse = async function(url) {
// Secure the URL // Secure the URL
const secureURL = exports.enforceHttpsUrl(url); const secureURL = exports.enforceHttpsUrl(url);
@ -162,10 +172,10 @@ module.exports.fetchGETResponse = async function(url) {
* @protected * @protected
* Enforces the scheme of the URL is https and returns the new URL. * Enforces the scheme of the URL is https and returns the new URL.
* @param {String} url * @param {String} url
* @returns {String} * @returns {String} Secure URL or `null` if the argument is not a string
*/ */
module.exports.enforceHttpsUrl = function (url) { 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 * @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 * @param {String} url String to check for correctness
* @returns {Boolean} true if the string is a valid URL, false otherwise * @returns {Boolean} true if the string is a valid URL, false otherwise
*/ */
module.exports.isStringAValidURL = function (url) { module.exports.isStringAValidURL = function (url) {
try { // Many thanks to Daveo at StackOverflow (https://preview.tinyurl.com/y2f2e2pc)
new URL(url); // skipcq: JS-0078 const expression = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
return true; const regex = new RegExp(expression);
} catch (err) { if (url.match(regex)) return true;
return false; else return false;
}
}; };
/** /**

View File

@ -1,3 +1,4 @@
/* istanbul ignore file */
"use strict"; "use strict";
// Public modules from npm // Public modules from npm

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "f95api", "name": "f95api",
"version": "1.3.5", "version": "1.5.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,7 +1,7 @@
{ {
"main": "./app/index.js", "main": "./app/index.js",
"name": "f95api", "name": "f95api",
"version": "1.3.5", "version": "1.5.0",
"author": { "author": {
"name": "Millennium Earl" "name": "Millennium Earl"
}, },
@ -19,7 +19,10 @@
"scraping", "scraping",
"login", "login",
"game", "game",
"games" "games",
"data",
"userdata",
"user data"
], ],
"scripts": { "scripts": {
"unit-test-mocha": "nyc --reporter=text mocha './test/index-test.js'", "unit-test-mocha": "nyc --reporter=text mocha './test/index-test.js'",

290
test/index-test-legacy.js Normal file
View File

@ -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;
});
});

View File

@ -1,290 +1,36 @@
"use strict"; "use strict";
// Core modules // Test suite
const fs = require("fs"); 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 describe("Test basic function", function testBasic() {
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 () {
//#region Set-up //#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
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 //#endregion Set-up
let testOrder = 0; describe("Test credentials class", credentials.bind(this));
describe("Test network helper", network.bind(this));
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 () { describe("Test F95 modules", function testF95Modules() {
//#region Set-up //#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
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 //#endregion Set-up
it("Test with valid credentials", async function () { describe("Test scraper methods", scraper.bind(this));
const result = await F95API.login(USERNAME, PASSWORD); describe("Test searcher methods", searcher.bind(this));
expect(result.success).to.be.true; describe("Test user scraper methods", uScraper.bind(this));
expect(result.message).equal("Logged with cookies");
});
}); });
describe("Load base data without cookies", function () { describe("Test complete API", function testAPI() {
//#region Set-up //#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
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 //#endregion Set-up
it("With login", async function () { describe("Test API", api.bind(this));
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;
});
}); });

64
test/suites/api-test.js Normal file
View File

@ -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;
});
};

View File

@ -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

View File

@ -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("<!DOCTYPE html>")).to.be.true;
});
};

View File

@ -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;
});
};

View File

@ -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

View File

@ -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