Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"istanbul-lib-report": "^3.0.0",
"istanbul-reports": "^3.0.1",
"lerna": "^3.20.2",
"node-fetch": "^2.6.0",
"prettier": "^1.19.1"
}
}
1 change: 1 addition & 0 deletions packages/target-decisioning-engine/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules/**
coverage/**
babel.config.js
decisioning-payloads.js
test/artifact/**
3 changes: 2 additions & 1 deletion packages/target-decisioning-engine/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const baseConfig = require("@adobe/target-tools/jest.config.js");

module.exports = {
...baseConfig
...baseConfig,
setupFiles: ["core-js", "./jest.polyfills.js"]
};
1 change: 1 addition & 0 deletions packages/target-decisioning-engine/jest.polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "fast-text-encoding";
6 changes: 6 additions & 0 deletions packages/target-decisioning-engine/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/target-decisioning-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"access": "public"
},
"scripts": {
"lint": "eslint src/** test/**",
"lint": "eslint src/**/*.js test/**/*.js",
"clean": "rimraf dist",
"prebuild": "npm run clean && npm run lint",
"build": "NODE_ENV=production rollup -c",
Expand Down Expand Up @@ -42,6 +42,7 @@
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jest": "^23.6.0",
"eslint-plugin-prettier": "^3.1.0",
"fast-text-encoding": "^1.0.3",
"http-status-codes": "^1.4.0",
"https-proxy-agent": "^5.0.0",
"jest": "^25.1.0",
Expand Down
25 changes: 23 additions & 2 deletions packages/target-decisioning-engine/src/artifactProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ import {
} from "@adobe/target-tools";
import Messages from "./messages";
import {
ARTIFACT_FORMAT_BINARY,
DEFAULT_POLLING_INTERVAL,
LOG_PREFIX,
MINIMUM_POLLING_INTERVAL,
NUM_FETCH_RETRIES
} from "./constants";
import { ArtifactTracer } from "./traceProvider";
import { determineArtifactLocation } from "./utils";
import { determineArtifactFormat, determineArtifactLocation } from "./utils";
import {
ARTIFACT_DOWNLOAD_FAILED,
ARTIFACT_DOWNLOAD_SUCCEEDED,
GEO_LOCATION_UPDATED
} from "./events";
import { createGeoObjectFromHeaders } from "./geoProvider";
import ObfuscationProvider from "./obfuscationProvider";

const LOG_TAG = `${LOG_PREFIX}.ArtifactProvider`;
const NOT_MODIFIED = 304;
Expand All @@ -35,6 +37,7 @@ const OK = 200;
function ArtifactProvider(config) {
const logger = getLogger(config.logger);
const { eventEmitter = noop } = config;
const obfuscationProvider = ObfuscationProvider(config);

function getPollingInterval() {
if (
Expand Down Expand Up @@ -73,6 +76,11 @@ function ArtifactProvider(config) {
? config.artifactLocation
: determineArtifactLocation(config);

const artifactFormat =
typeof config.artifactFormat === "string"
? config.artifactFormat
: determineArtifactFormat(artifactLocation);

const fetchWithRetry = getFetchWithRetry(
fetchApi,
NUM_FETCH_RETRIES,
Expand All @@ -95,6 +103,19 @@ function ArtifactProvider(config) {
);
}

/**
*
* @param {import("node-fetch").Response} res
* @return {Promise<import("../types/DecisioningArtifact").DecisioningArtifact>}
*/
function deobfuscate(res) {
return artifactFormat === ARTIFACT_FORMAT_BINARY
? res
.arrayBuffer()
.then(buffer => obfuscationProvider.deobfuscate(buffer))
: res.json();
}

function fetchArtifact(artifactUrl) {
const headers = {};
logger.debug(`${LOG_TAG} fetching artifact - ${artifactUrl}`);
Expand All @@ -115,7 +136,7 @@ function ArtifactProvider(config) {
}

if (res.ok && res.status === OK) {
return res.json().then(responseData => {
return deobfuscate(res).then(responseData => {
const etag = res.headers.get("Etag");
if (etag != null && isDefined(etag)) {
lastResponseData = responseData;
Expand Down
54 changes: 27 additions & 27 deletions packages/target-decisioning-engine/src/artifactProvider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ENVIRONMENT_PROD, ENVIRONMENT_STAGE } from "@adobe/target-tools";
import ArtifactProvider from "./artifactProvider";
import * as constants from "./constants";
import {
ARTIFACT_FILENAME,
ARTIFACT_FORMAT_JSON,
CDN_BASE_PROD,
CDN_BASE_STAGE,
SUPPORTED_ARTIFACT_MAJOR_VERSION
Expand All @@ -21,6 +23,13 @@ require("jest-fetch-mock").enableMocks();
describe("artifactProvider", () => {
let provider;

const TEST_CONF = {
client: "clientId",
organizationId: "orgId",
pollingInterval: 0,
artifactFormat: ARTIFACT_FORMAT_JSON // setting this tells the artifactProvider deobfuscation is not needed
};

beforeEach(() => {
fetch.resetMocks();
constants.MINIMUM_POLLING_INTERVAL = 0;
Expand All @@ -35,8 +44,7 @@ describe("artifactProvider", () => {
fetch.mockResponse(JSON.stringify(DUMMY_ARTIFACT_PAYLOAD));

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
artifactPayload: DUMMY_ARTIFACT_PAYLOAD,
maximumWaitReady: 500
});
Expand All @@ -48,8 +56,7 @@ describe("artifactProvider", () => {
fetch.mockResponse(JSON.stringify(DUMMY_ARTIFACT_PAYLOAD));

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
pollingInterval: 10
});

Expand All @@ -67,8 +74,7 @@ describe("artifactProvider", () => {
fetch.mockResponse(JSON.stringify(DUMMY_ARTIFACT_PAYLOAD));

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
pollingInterval: 10
});

Expand All @@ -87,8 +93,7 @@ describe("artifactProvider", () => {
expect.assertions(1);

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
artifactPayload: DUMMY_ARTIFACT_PAYLOAD,
pollingInterval: 10
});
Expand Down Expand Up @@ -119,8 +124,7 @@ describe("artifactProvider", () => {
);

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
pollingInterval: 0
});

Expand Down Expand Up @@ -155,8 +159,7 @@ describe("artifactProvider", () => {
};

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
pollingInterval: 0,
logger
});
Expand All @@ -174,8 +177,7 @@ describe("artifactProvider", () => {
.doMockIf(artifactURL);

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
pollingInterval: 0,
artifactLocation: artifactURL
});
Expand Down Expand Up @@ -265,7 +267,7 @@ describe("artifactProvider", () => {
client: "clientId",
organizationId: "orgId",
pollingInterval: 100,
artifactLocation: "rules.json"
artifactFormat: ARTIFACT_FORMAT_JSON
});

provider.subscribe(artifactUpdated);
Expand All @@ -288,16 +290,15 @@ describe("artifactProvider", () => {
expect(payload).toEqual(
expect.objectContaining({
artifactLocation:
"https://assets.adobetarget.com/clientId/production/v1/rules.json",
"https://assets.adobetarget.com/clientId/production/v1/rules.bin",
artifactPayload: expect.any(Object)
})
);
setTimeout(() => done(), 100);
}

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
pollingInterval: 0,
eventEmitter
});
Expand All @@ -313,7 +314,7 @@ describe("artifactProvider", () => {
expect(payload).toEqual(
expect.objectContaining({
artifactLocation:
"https://assets.adobetarget.com/clientId/production/v1/rules.json",
"https://assets.adobetarget.com/clientId/production/v1/rules.bin",
error: expect.objectContaining({
stack: expect.any(String),
message: "Forbidden"
Expand All @@ -324,8 +325,7 @@ describe("artifactProvider", () => {
}

provider = await ArtifactProvider({
client: "clientId",
organizationId: "orgId",
...TEST_CONF,
pollingInterval: 0,
eventEmitter
});
Expand All @@ -340,7 +340,7 @@ describe("determineArtifactLocation", () => {
cdnEnvironment: "staging"
})
).toEqual(
`${CDN_BASE_STAGE}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/rules.json`
`${CDN_BASE_STAGE}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -350,7 +350,7 @@ describe("determineArtifactLocation", () => {
client: "someClientId"
})
).toEqual(
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/rules.json`
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -361,7 +361,7 @@ describe("determineArtifactLocation", () => {
environment: ENVIRONMENT_STAGE
})
).toEqual(
`${CDN_BASE_PROD}/someClientId/${ENVIRONMENT_STAGE}/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/rules.json`
`${CDN_BASE_PROD}/someClientId/${ENVIRONMENT_STAGE}/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -380,7 +380,7 @@ describe("determineArtifactLocation", () => {
}
})
).toEqual(
`${CDN_BASE_PROD}/someClientId/${ENVIRONMENT_PROD}/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/rules.json`
`${CDN_BASE_PROD}/someClientId/${ENVIRONMENT_PROD}/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -391,7 +391,7 @@ describe("determineArtifactLocation", () => {
propertyToken: "xyz-123-abc"
})
).toEqual(
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/rules.json`
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/${ARTIFACT_FILENAME}`
);
});

Expand All @@ -405,7 +405,7 @@ describe("determineArtifactLocation", () => {
true
)
).toEqual(
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/xyz-123-abc/rules.json`
`${CDN_BASE_PROD}/someClientId/production/v${SUPPORTED_ARTIFACT_MAJOR_VERSION}/xyz-123-abc/${ARTIFACT_FILENAME}`
);
});
});
6 changes: 6 additions & 0 deletions packages/target-decisioning-engine/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export const DEFAULT_POLLING_INTERVAL = 300000; // five minutes (in milliseconds
export const MINIMUM_POLLING_INTERVAL = 300000; // five minutes (in milliseconds)
export const NUM_FETCH_RETRIES = 10;
export const SUPPORTED_ARTIFACT_MAJOR_VERSION = 1;
export const SUPPORTED_ARTIFACT_OBFUSCATION_VERSION = 1;
export const ARTIFACT_FILENAME = "rules.bin";
export const REGEX_ARTIFACT_FILENAME_BINARY = /.+\.bin$/i;
export const ARTIFACT_FORMAT_BINARY = "bin";
export const ARTIFACT_FORMAT_JSON = "json";

export const LOG_PREFIX = "LD";

export const CDN_BASE_PROD = "https://assets.adobetarget.com";
Expand Down
32 changes: 31 additions & 1 deletion packages/target-decisioning-engine/src/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import * as HttpStatus from "http-status-codes";
import { isDefined } from "@adobe/target-tools";
import TargetDecisioningEngine from "./index";
import * as constants from "./constants";
import { SUPPORTED_ARTIFACT_MAJOR_VERSION } from "./constants";
import {
ARTIFACT_FORMAT_BINARY,
ARTIFACT_FORMAT_JSON,
SUPPORTED_ARTIFACT_MAJOR_VERSION
} from "./constants";
import Messages from "./messages";
import {
DUMMY_ARTIFACT_PAYLOAD,
Expand Down Expand Up @@ -39,6 +43,7 @@ const TARGET_REQUEST = {
const CONFIG = {
client: "clientId",
organizationId: "orgId",
artifactFormat: ARTIFACT_FORMAT_JSON, // setting this tells the artifactProvider deobfuscation is not needed
maximumWaitReady: 500
};

Expand Down Expand Up @@ -146,6 +151,31 @@ describe("TargetDecisioningEngine", () => {
});
});

it("provides an error if the artifact cannot be deobfuscated", () => {
return new Promise(done => {
expect.assertions(1);
const eventEmitter = jest.fn();

fetch.mockResponse(JSON.stringify(DUMMY_ARTIFACT_PAYLOAD));

TargetDecisioningEngine({
...CONFIG,
artifactFormat: ARTIFACT_FORMAT_BINARY,
artifactLocation: "rules.bin", // an obfuscated artifact
pollingInterval: 0,
eventEmitter
})
.then(instance => {
decisioning = instance;
})
.catch(err => {
// the decision provider is expecting an obfuscated artifact (rules.bin), but it received a raw artifact (rules.json) and deobfuscation failed
expect(err).toEqual(new Error(Messages.ARTIFACT_NOT_AVAILABLE));
done();
});
});
});

it("getOffers resolves", async () => {
fetch.mockResponse(JSON.stringify(DUMMY_ARTIFACT_PAYLOAD));

Expand Down
1 change: 1 addition & 0 deletions packages/target-decisioning-engine/src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const Messages = {
INVALID_ENVIRONMENT: (expectedEnvironment, defaultEnvironment) =>
`'${expectedEnvironment}' is not a valid target environment, defaulting to '${defaultEnvironment}'.`,
NOT_APPLICABLE: "Not Applicable",
ARTIFACT_OBFUSCATION_ERROR: "Unable to read artifact JSON",
UNKNOWN: "unknown"
};

Expand Down
Loading