Skip to content

Commit 1ef478e

Browse files
committed
feat(data-point): add tests and new verification of arguments
add checks for locals, tracer and cache arguments closes ViacomInc#395
1 parent 06a69ba commit 1ef478e

File tree

4 files changed

+324
-49
lines changed

4 files changed

+324
-49
lines changed

packages/data-point/src/DataPoint.js

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
const { createReducer } = require("./create-reducer");
2+
const { Accumulator } = require("./Accumulator");
3+
const { resolve } = require("./resolve");
4+
const { Cache } = require("./Cache");
5+
const isPlainObject = require("./is-plain-object");
6+
7+
/**
8+
* Applies a reducer from an accumulator object.
9+
*
10+
* @param {Accumulator} acc accumulator object to be used.
11+
* @param {Reducer} reducer
12+
* @returns {Promise<any>} resolved value
13+
*/
14+
async function resolveFromAccumulator(acc, reducer) {
15+
const parsedReducers = createReducer(reducer);
16+
return resolve(acc, parsedReducers);
17+
}
18+
19+
/**
20+
* Applies a reducer to the provided input value.
21+
*
22+
* @param {any} input value to process.
23+
* @param {Reducer} reducer valid reducer to process the value.
24+
* @param {Object} options
25+
* @param {Cache|undefined} options.cache cache manager, see Cache for options.
26+
* @param {Object|undefined} options.locals persistent object that is
27+
* accessible via the Accumulator object on every reducer.
28+
* @param {OpenTrace.Span|undefined} options.tracer when provided it should
29+
* comply with the **opentracing** Span API.
30+
* @returns {Promise<any>} resolved value
31+
*/
32+
async function resolveFromInput(input, reducer, options = {}) {
33+
const acc = new Accumulator({
34+
value: input,
35+
locals: options.locals,
36+
resolve: resolveFromAccumulator,
37+
cache: options.cache,
38+
tracer: options.tracer
39+
});
40+
41+
return resolveFromAccumulator(acc, reducer);
42+
}
43+
44+
/**
45+
* @param {Object} locals
46+
* @throws Error if the value is not a simple object. To kow which values are
47+
* considered as simple objects please read the unit test for `isPlainObject`.
48+
*/
49+
function validateLocals(locals) {
50+
if (locals === undefined || isPlainObject(locals)) {
51+
return;
52+
}
53+
54+
throw new Error("'options.locals' must be undefined or an object");
55+
}
56+
57+
/**
58+
* @param {OpenTrace.Span} tracer when provided it should
59+
* comply with the **opentracing** Span API.
60+
* @throws Error if the object does not expose the methods `startSpan`,
61+
* `setTag`, `log`.
62+
*/
63+
function validateTracer(tracer) {
64+
if (tracer) {
65+
if (typeof tracer.startSpan !== "function") {
66+
throw new Error(
67+
"tracer.startSpan must be a function, tracer expects opentracing API (see https://opentracing.io)"
68+
);
69+
}
70+
71+
if (typeof tracer.setTag !== "function") {
72+
throw new Error(
73+
"tracer.setTag must be a function, tracer expects opentracing API (see https://opentracing.io)"
74+
);
75+
}
76+
77+
if (typeof tracer.log !== "function") {
78+
throw new Error(
79+
"tracer.log must be a function, tracer expects opentracing API (see https://opentracing.io)"
80+
);
81+
}
82+
}
83+
}
84+
85+
class DataPoint {
86+
constructor() {
87+
Object.defineProperty(this, "cache", {
88+
enumerable: false,
89+
value: new Cache()
90+
});
91+
}
92+
93+
static create() {
94+
return new DataPoint();
95+
}
96+
97+
/**
98+
* @param {any} input value to run the provided reducer thru.
99+
* @param {Reducer} reducer reducer to process the input.
100+
* @param {Object} options
101+
* @param {Object|undefined} options.locals persistent object that is
102+
* accessible via the Accumulator object on every reducer.
103+
* @param {OpenTrace.Span|undefined} options.tracer when provided it should
104+
* comply with the **opentracing** Span API.
105+
* @returns {Promise<any>} result from running the input thru the
106+
* provided reducer.
107+
*/
108+
async resolve(input, reducer, options = {}) {
109+
validateLocals(options.locals);
110+
validateTracer(options.tracer);
111+
112+
return resolveFromInput(input, reducer, {
113+
...options,
114+
cache: this.cache
115+
});
116+
}
117+
}
118+
119+
module.exports = {
120+
resolveFromAccumulator,
121+
resolveFromInput,
122+
DataPoint,
123+
validateLocals,
124+
validateTracer
125+
};
+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
const dataPoint = require("./DataPoint");
2+
const { Accumulator } = require("./Accumulator");
3+
const { Cache } = require("./Cache");
4+
5+
const sayHello = value => `Hello ${value}`;
6+
const getAccumulator = (input, acc) => acc;
7+
8+
const shallowTracer = {
9+
startSpan: () => Object.create(shallowTracer),
10+
setTag: () => true,
11+
log: () => true
12+
};
13+
14+
describe("resolveFromAccumulator", () => {
15+
it("should run reducers using accumulator as input", async () => {
16+
const acc = new Accumulator({ value: "world" });
17+
const result = await dataPoint.resolveFromAccumulator(acc, sayHello);
18+
expect(result).toEqual("Hello world");
19+
});
20+
});
21+
22+
describe("resolveFromInput", () => {
23+
it("should run reducers against input", async () => {
24+
const result = await dataPoint.resolveFromInput("world", sayHello);
25+
expect(result).toEqual("Hello world");
26+
});
27+
28+
describe("passing options object to accumulator", () => {
29+
const options = {
30+
locals: {
31+
key: "value"
32+
},
33+
cache: {
34+
get: () => true,
35+
set: () => true
36+
},
37+
tracer: "tracer"
38+
};
39+
it("should pass locals object", async () => {
40+
const result = await dataPoint.resolveFromInput(
41+
"input",
42+
getAccumulator,
43+
options
44+
);
45+
expect(result).toHaveProperty("locals", options.locals);
46+
});
47+
48+
it("should pass cache object", async () => {
49+
const result = await dataPoint.resolveFromInput(
50+
"input",
51+
getAccumulator,
52+
options
53+
);
54+
expect(result).toHaveProperty("cache", options.cache);
55+
});
56+
57+
it("should pass tracer object", async () => {
58+
const result = await dataPoint.resolveFromInput(
59+
"input",
60+
getAccumulator,
61+
options
62+
);
63+
expect(result).toHaveProperty("tracer", options.tracer);
64+
});
65+
66+
it("should pass resolveFromAccumulator method", async () => {
67+
const nestedResolver = (input, acc) => `nested ${acc.value}`;
68+
69+
const resolveNestedReducer = (input, acc) => {
70+
return acc.resolve(nestedResolver);
71+
};
72+
73+
const result = await dataPoint.resolveFromInput(
74+
"input",
75+
resolveNestedReducer,
76+
options
77+
);
78+
expect(result).toEqual("nested input");
79+
});
80+
});
81+
});
82+
83+
describe("validateLocals", () => {
84+
it("should only allow undefined or plain object", () => {
85+
expect(() => {
86+
dataPoint.validateLocals();
87+
}).not.toThrow();
88+
89+
expect(() => {
90+
dataPoint.validateLocals({});
91+
}).not.toThrow();
92+
});
93+
94+
it("should throw error on any un-valid value", () => {
95+
expect(() => {
96+
dataPoint.validateLocals(null);
97+
}).toThrowErrorMatchingInlineSnapshot(
98+
`"'options.locals' must be undefined or an object"`
99+
);
100+
101+
expect(() => {
102+
dataPoint.validateLocals("string");
103+
}).toThrowErrorMatchingInlineSnapshot(
104+
`"'options.locals' must be undefined or an object"`
105+
);
106+
107+
expect(() => {
108+
dataPoint.validateLocals([]);
109+
}).toThrowErrorMatchingInlineSnapshot(
110+
`"'options.locals' must be undefined or an object"`
111+
);
112+
});
113+
});
114+
115+
describe("validateTracer", () => {
116+
it("should only allow undefined or well defined tracer span API", () => {
117+
expect(() => {
118+
dataPoint.validateTracer();
119+
}).not.toThrow();
120+
121+
expect(() => {
122+
dataPoint.validateTracer(shallowTracer);
123+
}).not.toThrow();
124+
});
125+
126+
it("should throw error on any un-valid value", () => {
127+
expect(() => {
128+
dataPoint.validateTracer({});
129+
}).toThrowErrorMatchingInlineSnapshot(
130+
`"tracer.startSpan must be a function, tracer expects opentracing API (see https://opentracing.io)"`
131+
);
132+
133+
expect(() => {
134+
dataPoint.validateTracer({
135+
startSpan: () => true
136+
});
137+
}).toThrowErrorMatchingInlineSnapshot(
138+
`"tracer.setTag must be a function, tracer expects opentracing API (see https://opentracing.io)"`
139+
);
140+
141+
expect(() => {
142+
dataPoint.validateTracer({
143+
startSpan: () => true,
144+
setTag: () => true
145+
});
146+
}).toThrowErrorMatchingInlineSnapshot(
147+
`"tracer.log must be a function, tracer expects opentracing API (see https://opentracing.io)"`
148+
);
149+
});
150+
});
151+
152+
describe("DataPoint", () => {
153+
const DataPoint = dataPoint.DataPoint;
154+
155+
describe("constructor", () => {
156+
it("should initialize cache", () => {
157+
const dp = new DataPoint();
158+
expect(dp.cache).toBeInstanceOf(Cache);
159+
});
160+
});
161+
162+
describe("create", () => {
163+
it("should create instance of itself", () => {
164+
const dp = DataPoint.create();
165+
expect(dp).toBeInstanceOf(DataPoint);
166+
});
167+
});
168+
169+
describe("resolve", () => {
170+
it("should resolve input", async () => {
171+
const dp = new DataPoint();
172+
const result = await dp.resolve("world", sayHello);
173+
expect(result).toEqual("Hello world");
174+
});
175+
176+
it("should pass cache object", async () => {
177+
const dp = new DataPoint();
178+
dp.cache.get = () => true;
179+
dp.cache.set = () => true;
180+
181+
const result = await dp.resolve("input", getAccumulator);
182+
expect(result).toHaveProperty("cache", dp.cache);
183+
});
184+
185+
it("should pass options object", async () => {
186+
const dp = new DataPoint();
187+
const options = {
188+
locals: {
189+
key: "value"
190+
},
191+
tracer: shallowTracer
192+
};
193+
const result = await dp.resolve("input", getAccumulator, options);
194+
expect(result).toHaveProperty("locals", options.locals);
195+
expect(result).toHaveProperty("tracer");
196+
});
197+
});
198+
});

packages/data-point/src/data-point.js

-48
This file was deleted.

packages/data-point/src/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
const { DataPoint } = require("./data-point");
1+
const { DataPoint } = require("./DataPoint");
22

33
module.exports = DataPoint.create;

0 commit comments

Comments
 (0)