Skip to content

Commit

Permalink
extended tests for ipinfo module
Browse files Browse the repository at this point in the history
  • Loading branch information
pk910 committed Mar 15, 2024
1 parent 2fbd668 commit 033a9f4
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 11 deletions.
4 changes: 1 addition & 3 deletions src/modules/ipinfo/IPInfoDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export class IPInfoDB extends FaucetModuleDB {
) as {Json: string};
if(!row)
return null;

return JSON.parse(row.Json);
}

Expand All @@ -58,8 +57,7 @@ export class IPInfoDB extends FaucetModuleDB {

if(row) {
await this.db.run("UPDATE IPInfoCache SET Json = ?, Timeout = ? WHERE IP = ?", [infoJson, timeout, ip.toLowerCase()]);
}
else {
} else {
await this.db.run("INSERT INTO IPInfoCache (IP, Json, Timeout) VALUES (?, ?, ?)", [ip.toLowerCase(), infoJson, timeout]);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/modules/ipinfo/IPInfoModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class IPInfoModule extends BaseModule<IIPInfoConfig> {
private ipInfoResolver: IPInfoResolver;
private ipInfoMatchRestrictions: [pattern: string, restriction: number | IIPInfoRestrictionConfig][];
private ipInfoMatchRestrictionsRefresh: number;
private sessionRewardFactorCacheTimeout: number = 30;

protected override async startModule(): Promise<void> {
this.ipInfoDb = await ServiceManager.GetService(FaucetDatabase).createModuleDb(IPInfoDB, this);
Expand All @@ -49,6 +50,7 @@ export class IPInfoModule extends BaseModule<IIPInfoConfig> {

protected override stopModule(): Promise<void> {
this.ipInfoDb.dispose();
this.ipInfoResolver.dispose();
return Promise.resolve();
}

Expand All @@ -67,9 +69,9 @@ export class IPInfoModule extends BaseModule<IIPInfoConfig> {
if(ipInfo.status !== "success" && this.moduleConfig.required)
throw new FaucetError("INVALID_IPINFO", "Error while checking your IP: " + ipInfo.status);
} catch(ex) {
ServiceManager.GetService(FaucetProcess).emitLog(FaucetLogLevel.WARNING, "Error while fetching IP-Info for " + remoteIp + ": " + ex.toString());
if(this.moduleConfig.required)
throw new FaucetError("INVALID_IPINFO", "Error while checking your IP: " + ex.toString());
ServiceManager.GetService(FaucetProcess).emitLog(FaucetLogLevel.WARNING, "Error while fetching IP-Info for " + remoteIp + ": " + ex.toString());
}
session.setSessionData("ipinfo.data", ipInfo);

Expand All @@ -87,7 +89,7 @@ export class IPInfoModule extends BaseModule<IIPInfoConfig> {
let refreshTime = session.getSessionModuleRef("ipinfo.restriction.time") || 0;
let now = Math.floor((new Date()).getTime() / 1000);
let sessionRestriction: IIPInfoRestriction;
if(now - refreshTime > 30) {
if(now - refreshTime > this.sessionRewardFactorCacheTimeout) {
sessionRestriction = this.getSessionRestriction(session);
session.setSessionModuleRef("ipinfo.restriction.time", Math.floor((new Date()).getTime() / 1000));
session.setSessionModuleRef("ipinfo.restriction.data", sessionRestriction);
Expand Down
17 changes: 12 additions & 5 deletions src/modules/ipinfo/IPInfoResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,20 @@ export class IPInfoResolver {
private ipInfoDb: IPInfoDB;
private ipInfoApi: string;
private ipInfoCache: {[ip: string]: [number, Promise<IIPInfo>]} = {};
private ipInfoCacheCleanupTimer: NodeJS.Timeout;
private ipInfoCacheCleanupInterval: number = 20 * 1000;
private ipInfoCacheCleanupTimeout: number = 6 * 60 * 60;

public constructor(ipInfoDb: IPInfoDB, api: string) {
this.ipInfoDb = ipInfoDb;
this.ipInfoApi = api;
setInterval(() => {
this.ipInfoCacheCleanupTimer = setInterval(() => {
this.cleanIpInfoCache();
}, 20 * 1000);
}, this.ipInfoCacheCleanupInterval);
}

public dispose() {
clearInterval(this.ipInfoCacheCleanupTimer);
}

public setApi(api: string) {
Expand All @@ -40,9 +47,9 @@ export class IPInfoResolver {
public async getIpInfo(ipAddr: string): Promise<IIPInfo> {
let cachedIpInfo = await this.ipInfoDb.getIPInfo(ipAddr);
if(cachedIpInfo)
return Promise.resolve(cachedIpInfo);
return cachedIpInfo;
if(this.ipInfoCache.hasOwnProperty(ipAddr))
return this.ipInfoCache[ipAddr][1];
return await this.ipInfoCache[ipAddr][1];

let ipApiUrl = this.ipInfoApi.replace(/{ip}/, ipAddr);
let promise = FetchUtil.fetch(ipApiUrl)
Expand Down Expand Up @@ -88,7 +95,7 @@ export class IPInfoResolver {
private cleanIpInfoCache() {
let now = Math.floor((new Date()).getTime() / 1000);
Object.keys(this.ipInfoCache).forEach((ipAddr) => {
if(now - this.ipInfoCache[ipAddr][0] > 6 * 60 * 60) {
if(now - this.ipInfoCache[ipAddr][0] > this.ipInfoCacheCleanupTimeout) {
delete this.ipInfoCache[ipAddr];
}
});
Expand Down
182 changes: 181 additions & 1 deletion tests/modules/IpInfoModule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import { bindTestStubs, unbindTestStubs, loadDefaultTestConfig, returnDelayedPro
import { FetchUtil } from '../../src/utils/FetchUtil.js';
import { ServiceManager } from '../../src/common/ServiceManager.js';
import { FaucetDatabase } from '../../src/db/FaucetDatabase.js';
import { ModuleManager } from '../../src/modules/ModuleManager.js';
import { ModuleHookAction, ModuleManager } from '../../src/modules/ModuleManager.js';
import { SessionManager } from '../../src/session/SessionManager.js';
import { faucetConfig } from '../../src/config/FaucetConfig.js';
import { FaucetError } from '../../src/common/FaucetError.js';
import { IIPInfoConfig } from '../../src/modules/ipinfo/IPInfoConfig.js';
import { FaucetSession } from '../../src/session/FaucetSession.js';
import { IPInfoModule } from '../../src/modules/ipinfo/IPInfoModule.js';
import { sleepPromise } from '../../src/utils/PromiseUtils.js';
import { FaucetProcess } from '../../src/common/FaucetProcess.js';


describe("Faucet module: ipinfo", () => {
Expand Down Expand Up @@ -138,6 +142,111 @@ describe("Faucet module: ipinfo", () => {
expect(error?.getCode()).to.equal("INVALID_IPINFO", "unexpected error code");
});

it("Start session from blocked IP", async () => {
faucetConfig.modules["ipinfo"] = {
enabled: true,
apiUrl: "http://test-api-info-check.com/{ip}",
cacheTime: 86400,
required: true,
restrictions: {
hosting: 50,
US: {
reward: 1,
blocked: true,
},
},
restrictionsPattern: {},
restrictionsFile: null,
} as IIPInfoConfig;
globalStubs["fetch"].returns(returnDelayedPromise(true, {
json: () => Promise.resolve(testIPInfoResponse)
}));
await ServiceManager.GetService(ModuleManager).initialize();
let sessionManager = ServiceManager.GetService(SessionManager);
let error: FaucetError | null = null;
try {
await sessionManager.createSession("::ffff:8.8.8.8", {
addr: "0x0000000000000000000000000000000000001337",
});
} catch(ex) {
error = ex;
}
expect(error).to.not.equal(null, "no exception thrown");
expect(error instanceof FaucetError).to.equal(true, "unexpected error type");
expect(error?.getCode()).to.equal("IPINFO_RESTRICTION", "unexpected error code");
});

it("check ipinfo caching (no double request for same IP)", async () => {
faucetConfig.modules["ipinfo"] = {
enabled: true,
apiUrl: "http://test-api-info-check.com/{ip}",
cacheTime: 86400,
required: true,
restrictions: {
hosting: 100,
proxy: 50,
DE: 50,
},
restrictionsPattern: {},
restrictionsFile: null,
} as IIPInfoConfig;
globalStubs["fetch"].returns(returnDelayedPromise(true, {
json: () => Promise.resolve(testIPInfoResponse)
}));
await ServiceManager.GetService(ModuleManager).initialize();
let sessionManager = ServiceManager.GetService(SessionManager);
let testSession1 = await sessionManager.createSession("::ffff:8.8.8.8", {
addr: "0x0000000000000000000000000000000000001337",
});
expect(testSession1.getSessionStatus()).to.equal("claimable", "unexpected session 1 status");
expect(globalStubs["fetch"].callCount).to.equal(1, "unexpected fetch call count after session 2 start");
await sleepPromise(50);
let testSession2 = await sessionManager.createSession("::ffff:8.8.8.8", {
addr: "0x0000000000000000000000000000000000001338",
});
expect(testSession2.getSessionStatus()).to.equal("claimable", "unexpected session 2 status");
expect(globalStubs["fetch"].callCount).to.equal(1, "unexpected fetch call count after session 2 start");
});

it("check ipinfo caching (cache timeout)", async () => {
faucetConfig.modules["ipinfo"] = {
enabled: true,
apiUrl: "http://test-api-info-check.com/{ip}",
cacheTime: 86400,
required: true,
restrictions: {
hosting: 100,
proxy: 50,
DE: 50,
},
restrictionsPattern: {},
restrictionsFile: null,
} as IIPInfoConfig;
globalStubs["fetch"].returns(returnDelayedPromise(true, {
json: () => Promise.resolve(testIPInfoResponse)
}));
await ServiceManager.GetService(ModuleManager).initialize();
let sessionManager = ServiceManager.GetService(SessionManager);
let testSession1 = await sessionManager.createSession("::ffff:8.8.8.8", {
addr: "0x0000000000000000000000000000000000001337",
});
expect(testSession1.getSessionStatus()).to.equal("claimable", "unexpected session 1 status");
expect(globalStubs["fetch"].callCount).to.equal(1, "unexpected fetch call count after session 2 start");

let ipinfoModule = ServiceManager.GetService(ModuleManager).getModule<any>("ipinfo");
ipinfoModule.ipInfoResolver.ipInfoCacheCleanupTimeout = 0;
await ipinfoModule.ipInfoDb.cleanStore();
ipinfoModule.ipInfoDb.now = () => { return Math.floor((new Date()).getTime() / 1000) + 86405; };
await sleepPromise(1000);
ipinfoModule.ipInfoResolver.cleanIpInfoCache();

let testSession2 = await sessionManager.createSession("::ffff:8.8.8.8", {
addr: "0x0000000000000000000000000000000000001338",
});
expect(testSession2.getSessionStatus()).to.equal("claimable", "unexpected session 2 status");
expect(globalStubs["fetch"].callCount).to.equal(2, "unexpected fetch call count after session 2 start");
});

it("check ipinfo based restriction (no restriction)", async () => {
faucetConfig.modules["ipinfo"] = {
enabled: true,
Expand Down Expand Up @@ -279,4 +388,75 @@ describe("Faucet module: ipinfo", () => {
expect(testSession.getDropAmount()).to.equal(50n, "unexpected drop amount");
});

it("check refreshed restrictions on running session (block session)", async () => {
faucetConfig.modules["ipinfo"] = {
enabled: true,
apiUrl: "http://test-api-info-check.com/{ip}",
cacheTime: 86400,
required: false,
restrictions: {
},
restrictionsPattern: {},
restrictionsFile: null,
} as IIPInfoConfig;
globalStubs["fetch"].returns(returnDelayedPromise(true, {
json: () => Promise.resolve(testIPInfoResponse)
}));
await ServiceManager.GetService(ModuleManager).initialize();
ServiceManager.GetService(ModuleManager).addActionHook(null, ModuleHookAction.SessionStart, 100, "test-task", (session: FaucetSession, userInput: any) => {
session.addBlockingTask("test", "test1", 1);
});
ServiceManager.GetService(ModuleManager).getModule<any>("ipinfo").sessionRewardFactorCacheTimeout = 0;
let sessionManager = ServiceManager.GetService(SessionManager);
let testSession = await sessionManager.createSession("::ffff:8.8.8.8", {
addr: "0x0000000000000000000000000000000000001337",
});
expect(testSession.getSessionStatus()).to.equal("running", "unexpected session status before restriction update");
await testSession.addReward(50n);
(faucetConfig.modules["ipinfo"] as any).restrictions["US"] = {
blocked: true,
};
ServiceManager.GetService(FaucetProcess).emit("reload");
await sleepPromise(1000);
await testSession.addReward(10n);
await sleepPromise(100);
expect(testSession.getSessionStatus()).to.equal("claimable", "unexpected session status after restriction update");
expect(testSession.getDropAmount()).to.equal(60n, "unexpected drop amount");
});

it("check refreshed restrictions on running session (kill session)", async () => {
faucetConfig.modules["ipinfo"] = {
enabled: true,
apiUrl: "http://test-api-info-check.com/{ip}",
cacheTime: 86400,
required: false,
restrictions: {
},
restrictionsPattern: {},
restrictionsFile: null,
} as IIPInfoConfig;
globalStubs["fetch"].returns(returnDelayedPromise(true, {
json: () => Promise.resolve(testIPInfoResponse)
}));
await ServiceManager.GetService(ModuleManager).initialize();
ServiceManager.GetService(ModuleManager).addActionHook(null, ModuleHookAction.SessionStart, 100, "test-task", (session: FaucetSession, userInput: any) => {
session.addBlockingTask("test", "test1", 1);
});
ServiceManager.GetService(ModuleManager).getModule<any>("ipinfo").sessionRewardFactorCacheTimeout = 0;
let sessionManager = ServiceManager.GetService(SessionManager);
let testSession = await sessionManager.createSession("::ffff:8.8.8.8", {
addr: "0x0000000000000000000000000000000000001337",
});
expect(testSession.getSessionStatus()).to.equal("running", "unexpected session status before restriction update");
await testSession.addReward(50n);
(faucetConfig.modules["ipinfo"] as any).restrictions["US"] = {
blocked: "kill",
message: "bye bye"
};
await sleepPromise(1000);
await testSession.addReward(10n);
await sleepPromise(100);
expect(testSession.getSessionStatus()).to.equal("failed", "unexpected session status after restriction update");
});

});

0 comments on commit 033a9f4

Please sign in to comment.