import { describe, it } from "mocha"; import { expect } from "chai"; import FeatureConfigFake from "../fake/FeatureConfigFake"; import { FeatureConfigService } from "../../../src/services/FeatureConfig.service"; import { RedisService, RedisClient } from "../../../src/services/redis.service"; import CategoryService from "../../../src/services/category.service"; import { CategoryProviderType, ICategoryValue, CategoryRating, BrandSafetySeverity, EmptyCategoryValue, } from "../../../src/categories/categoryProvider"; import { sleep } from "../../../src/utils/helper"; import { ChoiceRequest, RequestInventorySource } from "../../../src/models/request.model"; import { FakeRequestBuilder } from "../fakeRequestBuilder"; import MockCategoryProvider from "../../../src/categories/mockCategoryProvider"; describe("CategoriesService", function () { const featureConfigService = FeatureConfigService.getInstance(); const mockedFeatureConf: FeatureConfigFake = new FeatureConfigFake(); featureConfigService.setFeatureConfig(mockedFeatureConf); let categoryService: CategoryService; let redisClient: RedisClient; before(async () => { redisClient = RedisService.getClient("mockedClient", false) as any; categoryService = new CategoryService(); categoryService.refreshTTLrandomness = 2000; }); after(async () => { redisClient.disconnect(); categoryService.close(); }); afterEach(async () => { await redisClient.client.flushall(); }); const testDataStatus = [ { url: "http://www.ogury.com/?status=safe", result: CategoryRating.Safe, content: "safe", key: "cat:mock:5ad6e100cafe1b02cd11dcd42da53599ab9519632d1d7eb6b9dab44b0683ffc3", app: false, ios: false, }, { url: "http://www.ogury.com/?status=unsafe", result: CategoryRating.Unsafe, content: "unsafe", key: "cat:mock:9c2174402bdc466dac24a75eb70376ee911819f6ceab42c446a42b3e743922d7", app: false, ios: false, }, { url: "http://www.ogury.com/?status=unknown", result: CategoryRating.Unknown, content: "unknown", key: "cat:mock:2b768f3bbf0f1cdc0ea59acb6a440a0745f47f71970bc3a954f941169019603e", app: false, ios: false, }, { url: "http://www.ogury.com/?status=unknown_unsafe", result: CategoryRating.Unsafe, content: "unknown_unsafe", key: "cat:mock:48c226137551b96da80d4e43b27c1997b20a803451909e9f35338c686a711532", app: false, ios: false, }, { url: "android.safe", result: CategoryRating.Safe, content: "safe", key: "cat:mock:35e889fbfa2df3c045927357de4acd83c5805cdbfe770ce0e9bf705e63dd76e6:android", app: true, ios: false, }, { url: "ios.safe", result: CategoryRating.Safe, content: "safe", key: "cat:mock:9e9aeaae867259fc4cdccb7283405fd26dd937281e6546aea56a9ec00af3bc87:ios", app: true, ios: true, }, ]; testDataStatus.forEach((data) => { it(`test for ${data.url} with app=${data.app} and ios=${data.ios} should be empty at first call and ${data.content} on second call`, async function () { const url = data.url; const request: ChoiceRequest = createRequest(url, data.app, data.ios); let result = await categoryService.getPublisherCategoryValue( request, CategoryProviderType.Mock ); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unknown); expect(request.categories).to.be.undefined; await sleep(80); result = await categoryService.getPublisherCategoryValue(request, CategoryProviderType.Mock); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(data.result); const redisValue = await redisClient.get(data.key); expect(redisValue).to.be.not.null; const redisElt: string[] = redisValue!.split("|"); expect(redisElt[0]).to.equal(data.content); expect(request.categories!.size).to.be.equal(1); const catValue: ICategoryValue = request.categories!.get(CategoryProviderType.Mock)!; expect(catValue).not.to.be.null; expect(catValue.toSegmentList()[0]).to.be.equal(data.content); }); }); const DeviceFraudDataStatus = [ { ip: "2.2.2.1", result: CategoryRating.Safe, content: "2.2.2.1", key: "cat:mock:0d2552814c8b65b7e7e5c09b320405d6bda6e42142945f3b3f147e5d6e00981d", app: false, ios: false, }, { ip: "2.2.2.2", result: CategoryRating.Unsafe, content: "2.2.2.2", key: "cat:mock:1e95611092391cacc5ad3adeeb93ed4d881d291e09b7954fd9e7886517739d7b", app: false, ios: false, }, { ip: "2.2.2.3", result: CategoryRating.Unknown, content: "2.2.2.3", key: "cat:mock:b3bed4392f3bdd617214baf2ee627f5b7cdbe730a77ecb9804a300ae39b2d2e4", app: false, ios: false, }, ]; DeviceFraudDataStatus.forEach((data) => { it(`test for ${data.ip} with app=${data.app} and ios=${data.ios} should be empty at first call and ${data.content} on second call`, async function () { const request: ChoiceRequest = createIpRequest(data.ip, data.app, data.ios); let result = await categoryService.getDeviceCategoryValue(request, CategoryProviderType.Mock); expect(result.getDeviceFraud()).to.equal(CategoryRating.Unknown); expect(request.categories).to.be.undefined; await sleep(80); result = await categoryService.getDeviceCategoryValue(request, CategoryProviderType.Mock); expect(result.getDeviceFraud()).to.equal(data.result); const redisValue = await redisClient.get(data.key); expect(redisValue).to.be.not.null; const redisElt: string[] = redisValue!.split("|"); expect(redisElt[0]).to.equal(data.content); expect(request.deviceCategories!.size).to.be.equal(1); expect(request.categories).to.be.undefined; const catValue: ICategoryValue = request.deviceCategories!.get(CategoryProviderType.Mock)!; expect(catValue).not.to.be.null; expect(catValue.getDeviceFraud()).to.be.equal(data.result); }); }); it("should be working with multiple calls", async function () { for (let i = 0; i < 100; i++) { const result: ICategoryValue = await categoryService.getPublisherCategoryValue( createRequest("http://www.ogury.com/?test&safe" + i), CategoryProviderType.Mock ); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unknown); } await sleep(120); for (let i: number = 0; i < 100; i++) { const result: ICategoryValue = await categoryService.getPublisherCategoryValue( createRequest("http://www.ogury.com/?test&safe" + i), CategoryProviderType.Mock ); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Safe); } }); it("refresh and expire time should be set correctly", async function () { const now: number = Date.now(); const url = "http://www.ogury.com?status=unsafe&refreshTime=" + (now + 5000) + "&expire=" + 10000; const request: ChoiceRequest = createRequest(url); let result: ICategoryValue = await categoryService.getPublisherCategoryValue( request, CategoryProviderType.Mock ); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unknown); expect(result.getBrandSafety(BrandSafetySeverity.Medium)).to.equal(CategoryRating.Unknown); expect(result.getBrandSafety(BrandSafetySeverity.Low)).to.equal(CategoryRating.Unknown); await sleep(80); result = await categoryService.getPublisherCategoryValue(request, CategoryProviderType.Mock); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unsafe); expect(result.getBrandSafety(BrandSafetySeverity.Medium)).to.equal(CategoryRating.Unsafe); expect(result.getBrandSafety(BrandSafetySeverity.Low)).to.equal(CategoryRating.Unsafe); const key: string = categoryService.encodeRedisKey( { device: false, urlOrBundle: url, provider: CategoryProviderType.Mock, requestSource: { environmentType: "web", deviceOS: "android" }, }, new MockCategoryProvider() ); const redisValue = await redisClient.get(key); expect(redisValue).to.be.not.null; const redisElt: string[] = redisValue!.split("|"); expect(redisElt[0]).to.equal("unsafe"); expect(parseInt(redisElt[1])).to.equal(now + 5000); const ttl: number = await redisClient.client.pttl(key); expect(ttl).to.be.lte(10000); expect(ttl).to.be.gte(9000); }); it("when locked update should not be made", async function () { const url = "http://www.ogury.com/lock?status=safe"; const lock = await redisClient.getRedisLock( "cat:mock:c9e7c0183fc3b57493bd577c3a162327e1a56c2a01255b8e511bae9188541f28", 5000 ); const request: ChoiceRequest = createRequest(url); let result = await categoryService.getPublisherCategoryValue( request, CategoryProviderType.Mock ); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unknown); await sleep(80); result = await categoryService.getPublisherCategoryValue(request, CategoryProviderType.Mock); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unknown); lock!.unlock(); }); it("when refresh time is not up update should not be made", async function () { const url = "http://www.ogury.com/noupdate?status=safe"; const now = Date.now(); await redisClient.setPX( "cat:mock:4f57f29bdcacb7066117eb48208f9e6764c4257136868bafa0455c67a08eb7f1", "unsafe|" + (now + 5000), 5000 ); const request: ChoiceRequest = createRequest(url); let result = await categoryService.getPublisherCategoryValue( request, CategoryProviderType.Mock ); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unsafe); await sleep(80); result = await categoryService.getPublisherCategoryValue(request, CategoryProviderType.Mock); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unsafe); }); it("when refresh time is up an update should be made", async function () { const url = "http://www.ogury.com/update?status=safe"; const now = Date.now(); await redisClient.setPX( "cat:mock:6cd239c4d095aa9d394db1305cf66abe7badf3b0964b215e1ae993134b1de7cf", "unsafe|" + (now - 5000), 5000 ); let request: ChoiceRequest = createRequest(url); let result = await categoryService.getPublisherCategoryValue( request, CategoryProviderType.Mock ); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unsafe); await sleep(80); request = createRequest(url); result = await categoryService.getPublisherCategoryValue(request, CategoryProviderType.Mock); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Safe); }); it("when get good value from cache for debug method", async function () { const url = "http://www.ogury.com/update?status=safe"; const now = Date.now(); let result = await categoryService.getBrandSafetyDebugPurpose( url, CategoryProviderType.Mock, true ); expect(result).to.deep.equal(new EmptyCategoryValue()); result = await categoryService.getBrandSafetyDebugPurpose(url, undefined, true); expect(result).to.deep.equal(new EmptyCategoryValue()); await redisClient.setPX( "cat:mock:6cd239c4d095aa9d394db1305cf66abe7badf3b0964b215e1ae993134b1de7cf", "unsafe|" + (now - 5000), 5000 ); result = await categoryService.getBrandSafetyDebugPurpose(url, CategoryProviderType.Mock, true); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Unsafe); }); it("when get good value from api for debug method", async function () { const url = "http://www.ogury.com/update?status=safe"; const now = Date.now(); await redisClient.setPX( "cat:mock:6cd239c4d095aa9d394db1305cf66abe7badf3b0964b215e1ae993134b1de7cf", "unsafe|" + (now - 5000), 5000 ); const result = await categoryService.getBrandSafetyDebugPurpose( url, CategoryProviderType.Mock, false ); expect(result.getBrandSafety(BrandSafetySeverity.High)).to.equal(CategoryRating.Safe); }); }); function createIpRequest(ip: string, app: boolean = false, ios: boolean = false): ChoiceRequest { const req = new FakeRequestBuilder(); if (app) { req .withIP(ip) .withEnvironmentType("in-app") .withDeviceOS(ios ? "ios" : "android") .withAppBundleOrDomain("ogury.com", ios); } else { req .withIP(ip) .withEnvironmentType("web") .withDeviceOS(ios ? "ios" : "android") .withPage("http://ogury.com") .withAppBundleOrDomain("ogury.com"); } const request = req.build(); Object.assign(request, { id: "1000", context: {}, user: {}, inventory_source: RequestInventorySource.INTERNAL, campaigns: [], campaignData: {}, }); return request; } function createRequest(url: string, app: boolean = false, ios: boolean = false): ChoiceRequest { const req = new FakeRequestBuilder(); if (app) { req .withEnvironmentType("in-app") .withDeviceOS(ios ? "ios" : "android") .withAppBundleOrDomain(url, ios); } else { req .withEnvironmentType("web") .withDeviceOS(ios ? "ios" : "android") .withPage(url) .withAppBundleOrDomain(url); } const request = req.build(); Object.assign(request, { id: "1000", context: {}, user: {}, inventory_source: RequestInventorySource.INTERNAL, campaigns: [], campaignData: {}, }); return request; }