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
4 changes: 2 additions & 2 deletions packages/target-decisioning-engine/src/artifactProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
ARTIFACT_DOWNLOAD_SUCCEEDED,
GEO_LOCATION_UPDATED
} from "./events";
import { createGeoObject } from "./geoProvider";
import { createGeoObjectFromHeaders } from "./geoProvider";

const LOG_TAG = `${LOG_PREFIX}.ArtifactProvider`;
const NOT_MODIFIED = 304;
Expand Down Expand Up @@ -120,7 +120,7 @@ async function ArtifactProvider(config) {
lastResponseData = responseData;
lastResponseEtag = etag;
}
geoContext = createGeoObject(res);
geoContext = createGeoObjectFromHeaders(res.headers);
emitNewArtifact(responseData, geoContext);
}
return responseData;
Expand Down
112 changes: 76 additions & 36 deletions packages/target-decisioning-engine/src/geoProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,70 @@ import {
HTTP_HEADER_GEO_REGION
} from "./constants";

const GEO_MAPPINGS = [
{
headerName: HTTP_HEADER_FORWARDED_FOR,
parseValue: value => value,
valueKey: "ipAddress"
},
{
headerName: HTTP_HEADER_GEO_LATITUDE,
parseValue: value => parseFloat(value),
valueKey: "latitude"
},
{
headerName: HTTP_HEADER_GEO_LONGITUDE,
parseValue: value => parseFloat(value),
valueKey: "longitude"
},
{
headerName: HTTP_HEADER_GEO_COUNTRY,
parseValue: value => value,
valueKey: "countryCode"
},
{
headerName: HTTP_HEADER_GEO_REGION,
parseValue: value => value,
valueKey: "stateCode"
},
{
headerName: HTTP_HEADER_GEO_CITY,
parseValue: value => value,
valueKey: "city"
}
];

/**
* @param { Response } fetchResponse;
*
* @param {Function} valueFn, function to lookup value by key
* @param initial
* @return {import("@adobe/target-tools/delivery-api-client/models/Geo").Geo}
*/
function mapGeoValues(valueFn, initial = {}) {
return GEO_MAPPINGS.reduce((result, mapping) => {
const value = valueFn.call(null, mapping.headerName);
if (value != null && isDefined(value)) {
// eslint-disable-next-line no-param-reassign
result[mapping.valueKey] = mapping.parseValue(value);
}
return result;
}, initial);
}

/**
* @param { import("node-fetch").Headers } geoHeaders;
* @return { import("@adobe/target-tools/delivery-api-client/models/Geo").Geo }
*/
export function createGeoObject(fetchResponse) {
const geoObject = {};
export function createGeoObjectFromHeaders(geoHeaders) {
return mapGeoValues(key => geoHeaders.get(key));
}

const ipAddress = fetchResponse.headers.get(HTTP_HEADER_FORWARDED_FOR);
if (ipAddress != null && isDefined(ipAddress)) {
geoObject.ipAddress = ipAddress;
}
const latitude = fetchResponse.headers.get(HTTP_HEADER_GEO_LATITUDE);
if (latitude != null && isDefined(latitude)) {
geoObject.latitude = parseFloat(latitude);
}
const longitude = fetchResponse.headers.get(HTTP_HEADER_GEO_LONGITUDE);
if (longitude != null && isDefined(longitude)) {
geoObject.longitude = parseFloat(longitude);
}
const countryCode = fetchResponse.headers.get(HTTP_HEADER_GEO_COUNTRY);
if (countryCode != null && isDefined(countryCode)) {
geoObject.countryCode = countryCode;
}
const stateCode = fetchResponse.headers.get(HTTP_HEADER_GEO_REGION);
if (stateCode != null && isDefined(stateCode)) {
geoObject.stateCode = stateCode;
}
const city = fetchResponse.headers.get(HTTP_HEADER_GEO_CITY);
if (city != null && isDefined(city)) {
geoObject.city = city;
}
return geoObject;
/**
* @param {Object.<string, any>} geoPayload
* @return { import("@adobe/target-tools/delivery-api-client/models/Geo").Geo }
*/
export function createGeoObjectFromPayload(geoPayload = {}) {
return mapGeoValues(key => geoPayload[key]);
}

/**
Expand All @@ -62,9 +94,9 @@ export function GeoProvider(config, artifact) {

/**
* @param {import("@adobe/target-tools/delivery-api-client/models/Geo").Geo} geoRequestContext
* @return { import("@adobe/target-tools/delivery-api-client/models/Geo").Geo }
* @return { Promise<import("@adobe/target-tools/delivery-api-client/models/Geo").Geo> }
*/
async function validGeoRequestContext(geoRequestContext = {}) {
function validGeoRequestContext(geoRequestContext = {}) {
// When ipAddress is the only geo value passed in to getOffers(), do IP-to-Geo lookup.
const geoLookupPath = getGeoLookupPath(config);

Expand All @@ -84,14 +116,22 @@ export function GeoProvider(config, artifact) {
headers[HTTP_HEADER_FORWARDED_FOR] = geoRequestContext.ipAddress;
}

const ipToGeoLookupResponse = await fetchApi(geoLookupPath, { headers });
const geoResponseHeaders = createGeoObject(ipToGeoLookupResponse);
Object.assign(geoRequestContext, geoResponseHeaders);

eventEmitter(GEO_LOCATION_UPDATED, { geoContext: geoRequestContext });
return fetchApi(geoLookupPath, {
headers
})
.then(geoResponse =>
geoResponse
.json()
.then(geoPayload => createGeoObjectFromPayload(geoPayload))
)
.then(fetchedGeoValues => {
Object.assign(geoRequestContext, fetchedGeoValues);
eventEmitter(GEO_LOCATION_UPDATED, { geoContext: geoRequestContext });
return geoRequestContext;
});
}

return geoRequestContext;
return Promise.resolve(geoRequestContext);
}
return {
validGeoRequestContext
Expand Down
94 changes: 73 additions & 21 deletions packages/target-decisioning-engine/src/geoProvider.spec.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { UNKNOWN_IP_ADDRESS } from "@adobe/target-tools";
import { createGeoObject, GeoProvider } from "./geoProvider";
import {
createGeoObject,
createGeoObjectFromHeaders,
createGeoObjectFromPayload,
GeoProvider
} from "./geoProvider";
import { DUMMY_ARTIFACT_PAYLOAD } from "../test/decisioning-payloads";
import { HTTP_HEADER_FORWARDED_FOR } from "./constants";

require("jest-fetch-mock").enableMocks();

describe("geoProvider", () => {
it("creates geo context from response", () => {
const headers = {
"x-geo-latitude": 37.773972,
"x-geo-longitude": -122.431297,
"x-geo-country-code": "US",
"x-geo-region-code": "CA",
"x-geo-city": "SANFRANCISCO"
};
const headers = {
"x-geo-latitude": 37.773972,
"x-geo-longitude": -122.431297,
"x-geo-country-code": "US",
"x-geo-region-code": "CA",
"x-geo-city": "SANFRANCISCO"
};

it("creates geo context from response headers", () => {
expect(
createGeoObject({
headers: {
get: key => headers[key]
}
createGeoObjectFromHeaders({
get: key => headers[key]
})
).toEqual({
latitude: 37.773972,
Expand All @@ -29,18 +33,30 @@ describe("geoProvider", () => {
});
});

it("creates geo context from response payload", () => {
expect(createGeoObjectFromPayload(headers)).toEqual({
latitude: 37.773972,
longitude: -122.431297,
city: "SANFRANCISCO",
countryCode: "US",
stateCode: "CA"
});
});

describe("validGeoRequestContext", () => {
const geoValues = {
"x-geo-longitude": -122.4,
"x-geo-latitude": 37.75,
"x-geo-city": "SAN FRANCISCO",
"x-geo-region-code": "CA",
"x-geo-country-code": "US"
};

beforeEach(() => {
fetch.resetMocks();

fetch.mockResponse(JSON.stringify({ status: "OK" }), {
headers: {
"X-GEO-Longitude": -122.4,
"X-GEO-Latitude": 37.75,
"X-GEO-City": "SAN FRANCISCO",
"X-GEO-Region-Code": "CA",
"X-GEO-Country-Code": "US"
}
fetch.mockResponse(JSON.stringify(geoValues), {
headers: geoValues
});
});

Expand Down Expand Up @@ -143,5 +159,41 @@ describe("geoProvider", () => {
city: "Reno"
});
});

it("gets geo details from payload if necessary (ie11 support)", async () => {
fetch.resetMocks();

// response with missing headers to simulate ie11 being unable to read them
fetch.mockResponse(JSON.stringify(geoValues), {
headers: {}
});

const { validGeoRequestContext } = GeoProvider(
{},
{
...DUMMY_ARTIFACT_PAYLOAD,
geoTargetingEnabled: true
}
);

const geo = await validGeoRequestContext({ ipAddress: "12.21.1.40" });

expect(fetch.mock.calls.length).toEqual(1);
expect(fetch.mock.calls[0][0]).toEqual(
"https://assets.adobetarget.com/v1/geo"
);
expect(fetch.mock.calls[0][1].headers[HTTP_HEADER_FORWARDED_FOR]).toEqual(
"12.21.1.40"
);

expect(geo).toEqual({
city: "SAN FRANCISCO",
countryCode: "US",
ipAddress: "12.21.1.40",
latitude: 37.75,
longitude: -122.4,
stateCode: "CA"
});
});
});
});
18 changes: 9 additions & 9 deletions packages/target-decisioning-engine/test/decisions.geo.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ describe("decisioning outcomes - geo params", () => {
});

it("can determine geo outcomes when geo context is missing but ipAddress exists in context", async () => {
fetch.mockResponse(JSON.stringify({ status: "OK" }), {
headers: {
"X-GEO-Longitude": -122.4,
"X-GEO-Latitude": 37.75,
"X-GEO-City": "SAN FRANCISCO",
"X-GEO-Region-Code": "CA",
"X-GEO-Country-Code": "US"
}
});
fetch.mockResponse(
JSON.stringify({
"x-geo-longitude": -122.4,
"x-geo-latitude": 37.75,
"x-geo-city": "SAN FRANCISCO",
"x-geo-region-code": "CA",
"x-geo-country-code": "US"
})
);

decisioning = await TargetDecisioningEngine({
...TEST_CONF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe("decisioning outcomes - params", () => {
},
eventToken:
"Zhwxeqy1O2r9Ske1YDA9bJNWHtnQtQrJfmRrQugEa2qCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==",
responseTokens: expect.any(Object),
responseTokens: expect.any(Object)
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe("decisioning outcomes - priority", () => {
content: "<div>kitty high with targeting: Firefox</div>",
eventToken:
"/DhjxnVDh9heBZ0MrYFLF2qipfsIHvVzTQxHolz2IpSCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==",
responseTokens: expect.any(Object),
responseTokens: expect.any(Object)
}
]
}
Expand Down Expand Up @@ -125,7 +125,7 @@ describe("decisioning outcomes - priority", () => {
content: "<div>kitty high A</div>",
eventToken:
"ruhwp7VESR7F74TJL2DV5WqipfsIHvVzTQxHolz2IpSCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==",
responseTokens: expect.any(Object),
responseTokens: expect.any(Object)
}
]
}
Expand Down
10 changes: 5 additions & 5 deletions packages/target-decisioning-engine/test/decisions.views.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ describe("decisioning outcomes - views", () => {
"HTML > BODY > DIV:nth-of-type(2) > IMG:nth-of-type(1)",
selector:
"HTML > BODY > DIV.offer:eq(0) > IMG:nth-of-type(1)",
type: "insertBefore",
type: "insertBefore"
}
],
responseTokens: expect.any(Object),
Expand Down Expand Up @@ -836,7 +836,7 @@ describe("decisioning outcomes - views", () => {
{
eventToken:
"39UdigzDfmb97ogXP1PN6wreqXMfVUcUx0s/BHR5kCKCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==",
responseTokens: expect.any(Object),
responseTokens: expect.any(Object)
},
{
content: [
Expand All @@ -852,12 +852,12 @@ describe("decisioning outcomes - views", () => {
type: "actions",
eventToken:
"39UdigzDfmb97ogXP1PN6wreqXMfVUcUx0s/BHR5kCKCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==",
responseTokens: expect.any(Object),
responseTokens: expect.any(Object)
},
{
eventToken:
"39UdigzDfmb97ogXP1PN6wreqXMfVUcUx0s/BHR5kCKCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==",
responseTokens: expect.any(Object),
responseTokens: expect.any(Object)
},
{
content: [
Expand All @@ -873,7 +873,7 @@ describe("decisioning outcomes - views", () => {
type: "actions",
eventToken:
"39UdigzDfmb97ogXP1PN6wreqXMfVUcUx0s/BHR5kCKCnQ9Y9OaLL2gsdrWQTvE54PwSz67rmXWmSnkXpSSS2Q==",
responseTokens: expect.any(Object),
responseTokens: expect.any(Object)
}
])
);
Expand Down