make-mp-data 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dungeons/adspend.js +96 -0
- package/dungeons/anon.js +104 -0
- package/dungeons/big.js +225 -0
- package/dungeons/business.js +345 -0
- package/dungeons/complex.js +396 -0
- package/dungeons/experiments.js +125 -0
- package/dungeons/foobar.js +241 -0
- package/dungeons/funnels.js +272 -0
- package/dungeons/gaming.js +315 -0
- package/dungeons/media.js +7 -7
- package/dungeons/mirror.js +129 -0
- package/dungeons/sanity.js +113 -0
- package/dungeons/scd.js +205 -0
- package/dungeons/simple.js +195 -0
- package/dungeons/userAgent.js +190 -0
- package/entry.js +57 -0
- package/index.js +96 -68
- package/lib/cli/cli.js +10 -5
- package/lib/core/config-validator.js +28 -12
- package/lib/core/context.js +147 -130
- package/lib/core/storage.js +45 -31
- package/lib/data/defaults.js +2 -2
- package/lib/generators/adspend.js +1 -2
- package/lib/generators/events.js +35 -24
- package/lib/generators/funnels.js +1 -2
- package/lib/generators/mirror.js +1 -2
- package/lib/orchestrators/mixpanel-sender.js +15 -7
- package/lib/orchestrators/user-loop.js +21 -10
- package/lib/orchestrators/worker-manager.js +5 -2
- package/lib/utils/ai.js +36 -63
- package/lib/utils/chart.js +5 -0
- package/lib/utils/instructions.txt +593 -0
- package/lib/utils/utils.js +162 -38
- package/package.json +23 -9
- package/types.d.ts +376 -376
- package/.claude/settings.local.json +0 -20
- package/.gcloudignore +0 -18
- package/.gitattributes +0 -2
- package/.prettierrc +0 -0
- package/.vscode/launch.json +0 -80
- package/.vscode/settings.json +0 -69
- package/.vscode/tasks.json +0 -12
- package/dungeons/customers/.gitkeep +0 -0
- package/env.yaml +0 -1
- package/lib/cloud-function.js +0 -20
- package/scratch.mjs +0 -62
- package/scripts/dana.mjs +0 -137
- package/scripts/deploy.sh +0 -15
- package/scripts/jsdoctest.js +0 -5
- package/scripts/new-dungeon.sh +0 -98
- package/scripts/new-project.mjs +0 -14
- package/scripts/run-index.sh +0 -2
- package/scripts/update-deps.sh +0 -5
- package/tests/benchmark/concurrency.mjs +0 -52
- package/tests/cli.test.js +0 -124
- package/tests/coverage/.gitkeep +0 -0
- package/tests/e2e.test.js +0 -379
- package/tests/int.test.js +0 -715
- package/tests/testCases.mjs +0 -229
- package/tests/testSoup.mjs +0 -28
- package/tests/unit.test.js +0 -910
- package/tmp/.gitkeep +0 -0
- package/tsconfig.json +0 -18
- package/vitest.config.js +0 -47
package/tests/int.test.js
DELETED
|
@@ -1,715 +0,0 @@
|
|
|
1
|
-
import dayjs from "dayjs";
|
|
2
|
-
import utc from "dayjs/plugin/utc.js";
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import * as u from 'ak-tools';
|
|
5
|
-
dayjs.extend(utc);
|
|
6
|
-
import 'dotenv/config';
|
|
7
|
-
import path from 'path';
|
|
8
|
-
|
|
9
|
-
/** @typedef {import('../types').Dungeon} Config */
|
|
10
|
-
/** @typedef {import('../types').EventConfig} EventConfig */
|
|
11
|
-
/** @typedef {import("../types").EventSchema} EventSchema */
|
|
12
|
-
/** @typedef {import('../types').ValueValid} ValueValid */
|
|
13
|
-
/** @typedef {import('../types').HookedArray} hookArray */
|
|
14
|
-
/** @typedef {import('../types').hookArrayOptions} hookArrayOptions */
|
|
15
|
-
/** @typedef {import('../types').Person} Person */
|
|
16
|
-
/** @typedef {import('../types').Funnel} Funnel */
|
|
17
|
-
/** @typedef {import('../types').UserProfile} UserProfile */
|
|
18
|
-
/** @typedef {import('../types').SCDSchema} SCDSchema */
|
|
19
|
-
/** @typedef {import('../types').Storage} Storage */
|
|
20
|
-
|
|
21
|
-
// Import main function
|
|
22
|
-
import main from '../index.js';
|
|
23
|
-
|
|
24
|
-
// Import generators directly
|
|
25
|
-
import { makeAdSpend } from '../lib/generators/adspend.js';
|
|
26
|
-
import { makeEvent } from '../lib/generators/events.js';
|
|
27
|
-
import { makeFunnel } from '../lib/generators/funnels.js';
|
|
28
|
-
import { makeProfile } from '../lib/generators/profiles.js';
|
|
29
|
-
import { makeSCD } from '../lib/generators/scd.js';
|
|
30
|
-
import { makeMirror } from '../lib/generators/mirror.js';
|
|
31
|
-
|
|
32
|
-
// Import orchestrators directly
|
|
33
|
-
import { sendToMixpanel } from '../lib/orchestrators/mixpanel-sender.js';
|
|
34
|
-
import { userLoop } from '../lib/orchestrators/user-loop.js';
|
|
35
|
-
import { validateDungeonConfig } from '../lib/core/config-validator.js';
|
|
36
|
-
|
|
37
|
-
// Import utilities directly
|
|
38
|
-
import { createHookArray } from '../lib/core/storage.js';
|
|
39
|
-
import { inferFunnels } from '../lib/core/config-validator.js';
|
|
40
|
-
import { validEvent } from '../lib/utils/utils.js';
|
|
41
|
-
import { createContext } from '../lib/core/context.js';
|
|
42
|
-
import { validateDungeonConfig } from '../lib/core/config-validator.js';
|
|
43
|
-
|
|
44
|
-
// Alias for compatibility
|
|
45
|
-
const hookArray = createHookArray;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Creates a test context object for generator function testing
|
|
49
|
-
* @param {Object} configOverrides - Config overrides for testing
|
|
50
|
-
* @returns {Object} Test context object
|
|
51
|
-
*/
|
|
52
|
-
function createTestContext(configOverrides = {}) {
|
|
53
|
-
const baseConfig = {
|
|
54
|
-
numEvents: 100,
|
|
55
|
-
numUsers: 10,
|
|
56
|
-
numDays: 30,
|
|
57
|
-
hasAdSpend: false,
|
|
58
|
-
hasLocation: false,
|
|
59
|
-
hasAvatar: false,
|
|
60
|
-
verbose: false,
|
|
61
|
-
writeToDisk: false,
|
|
62
|
-
isAnonymous: false,
|
|
63
|
-
hasAnonIds: false,
|
|
64
|
-
hasSessionIds: false,
|
|
65
|
-
concurrency: 1,
|
|
66
|
-
...configOverrides
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const validatedConfig = validateDungeonConfig(baseConfig);
|
|
70
|
-
const context = createContext(validatedConfig);
|
|
71
|
-
|
|
72
|
-
return context;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Mock the global variables
|
|
77
|
-
let CAMPAIGNS;
|
|
78
|
-
let DEFAULTS;
|
|
79
|
-
let STORAGE;
|
|
80
|
-
let CONFIG;
|
|
81
|
-
import { campaigns, devices, locations } from '../lib/data/defaults.js';
|
|
82
|
-
|
|
83
|
-
beforeEach(async () => {
|
|
84
|
-
// Reset global variables before each test
|
|
85
|
-
CAMPAIGNS = [
|
|
86
|
-
{ utm_campaign: ["campaign1", "campaign2"], utm_source: ["source1"], utm_medium: ["medium1"], utm_content: ["content1"], utm_term: ["term1"] }
|
|
87
|
-
];
|
|
88
|
-
DEFAULTS = {
|
|
89
|
-
locationsUsers: () => ({ city: 'San Francisco' }),
|
|
90
|
-
locationsEvents: () => ({ city: 'San Francisco' }),
|
|
91
|
-
iOSDevices: () => 'iPhone',
|
|
92
|
-
androidDevices: () => 'Android',
|
|
93
|
-
desktopDevices: () => 'Desktop',
|
|
94
|
-
browsers: () => 'Chrome',
|
|
95
|
-
campaigns: () => 'campaign1'
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
/** @type {Storage} */
|
|
99
|
-
STORAGE = {
|
|
100
|
-
eventData: await hookArray([], {}),
|
|
101
|
-
userProfilesData: await hookArray([], {}),
|
|
102
|
-
adSpendData: await hookArray([], {}),
|
|
103
|
-
scdTableData: [await hookArray([], {})],
|
|
104
|
-
groupProfilesData: await hookArray([], {}),
|
|
105
|
-
lookupTableData: await hookArray([], {}),
|
|
106
|
-
mirrorEventData: await hookArray([], {})
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/** @type {Config} */
|
|
111
|
-
CONFIG = {
|
|
112
|
-
numUsers: 10,
|
|
113
|
-
numEvents: 100,
|
|
114
|
-
numDays: 30,
|
|
115
|
-
simulationName: 'TestSimulation',
|
|
116
|
-
hook: (record) => record
|
|
117
|
-
};
|
|
118
|
-
global.CAMPAIGNS = CAMPAIGNS;
|
|
119
|
-
global.DEFAULTS = DEFAULTS;
|
|
120
|
-
global.STORAGE = STORAGE;
|
|
121
|
-
global.CONFIG = CONFIG;
|
|
122
|
-
const FIXED_NOW = dayjs('2024-02-02').unix();
|
|
123
|
-
global.FIXED_NOW = FIXED_NOW;
|
|
124
|
-
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
beforeEach(() => {
|
|
128
|
-
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe.sequential('generators', () => {
|
|
132
|
-
|
|
133
|
-
test('adspend: works', async () => {
|
|
134
|
-
const campaigns = [{
|
|
135
|
-
utm_source: ["foo"],
|
|
136
|
-
utm_campaign: ["one"],
|
|
137
|
-
utm_medium: ["two"],
|
|
138
|
-
utm_content: ["three"],
|
|
139
|
-
utm_term: ["four"]
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
utm_source: ["bar"],
|
|
143
|
-
utm_campaign: ["five"],
|
|
144
|
-
utm_medium: ["six"],
|
|
145
|
-
utm_content: ["seven"],
|
|
146
|
-
utm_term: ["eight"]
|
|
147
|
-
}];
|
|
148
|
-
const context = createTestContext({ hasAdSpend: true });
|
|
149
|
-
const result = await makeAdSpend(context, dayjs().subtract(30, 'day').toISOString(), campaigns);
|
|
150
|
-
expect(result.length).toBe(2);
|
|
151
|
-
expect(result[0]).toHaveProperty('event', '$ad_spend');
|
|
152
|
-
expect(result[1]).toHaveProperty('event', '$ad_spend');
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('adspend: empty', async () => {
|
|
156
|
-
const context = createTestContext({ hasAdSpend: true });
|
|
157
|
-
const result = await makeAdSpend(context, dayjs().subtract(30, 'day').toISOString(), []);
|
|
158
|
-
expect(result.length).toBe(0);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('adspend: external', async () => {
|
|
162
|
-
const campaigns = [
|
|
163
|
-
{ utm_source: ["source1"], utm_campaign: ["one"], utm_medium: ["two"], utm_content: ["three"], utm_term: ["four"] },
|
|
164
|
-
{ utm_source: ["source2"], utm_campaign: ["two"], utm_medium: ["three"], utm_content: ["four"], utm_term: ["five"] }
|
|
165
|
-
];
|
|
166
|
-
const context = createTestContext({ hasAdSpend: true });
|
|
167
|
-
const result = await makeAdSpend(context, dayjs().subtract(30, 'day').toISOString(), campaigns);
|
|
168
|
-
expect(result.length).toBe(2);
|
|
169
|
-
result.forEach(event => {
|
|
170
|
-
expect(event).toHaveProperty('event', '$ad_spend');
|
|
171
|
-
expect(event).toHaveProperty('utm_campaign');
|
|
172
|
-
expect(event).toHaveProperty('utm_source');
|
|
173
|
-
expect(event).toHaveProperty('cost');
|
|
174
|
-
expect(event).toHaveProperty('clicks');
|
|
175
|
-
expect(event).toHaveProperty('impressions');
|
|
176
|
-
expect(event).toHaveProperty('views');
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
test('makeEvent: works', async () => {
|
|
182
|
-
/** @type {EventConfig} */
|
|
183
|
-
const eventConfig = {
|
|
184
|
-
event: "test_event",
|
|
185
|
-
properties: {
|
|
186
|
-
prop1: ["value1", "value2"],
|
|
187
|
-
prop2: ["value3", "value4"],
|
|
188
|
-
prop3: ["value5"]
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
const context = createTestContext();
|
|
192
|
-
const result = await makeEvent(context, "known_id", dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), eventConfig, ["anon_id"], ["session_id"]);
|
|
193
|
-
expect(result).toHaveProperty('event', 'test_event');
|
|
194
|
-
expect(result).toHaveProperty('device_id', 'anon_id');
|
|
195
|
-
// expect(result).toHaveProperty('user_id', 'known_id'); // Known ID not always on the event
|
|
196
|
-
expect(result).toHaveProperty('session_id', 'session_id');
|
|
197
|
-
expect(result).toHaveProperty('source', 'dm4');
|
|
198
|
-
expect(result).toHaveProperty('insert_id');
|
|
199
|
-
expect(result).toHaveProperty('time');
|
|
200
|
-
expect(result).toHaveProperty('prop1');
|
|
201
|
-
expect(result).toHaveProperty('prop2');
|
|
202
|
-
expect(result.prop1 === "value1" || result.prop1 === "value2").toBeTruthy();
|
|
203
|
-
expect(result.prop2 === "value3" || result.prop2 === "value4").toBeTruthy();
|
|
204
|
-
expect(result).toHaveProperty('prop3', 'value5');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test('makeEvent: opt params', async () => {
|
|
208
|
-
const eventConfig = { event: "test_event", properties: {} };
|
|
209
|
-
const context = createTestContext();
|
|
210
|
-
const result = await makeEvent(context, "known_id", dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), eventConfig);
|
|
211
|
-
expect(result).toHaveProperty('event', 'test_event');
|
|
212
|
-
expect(result).toHaveProperty('user_id', 'known_id');
|
|
213
|
-
expect(result).toHaveProperty('source', 'dm4');
|
|
214
|
-
expect(result).toHaveProperty('insert_id');
|
|
215
|
-
expect(result).toHaveProperty('time');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test('makeEvent: correct defaults', async () => {
|
|
219
|
-
const eventConfig = {
|
|
220
|
-
event: "test_event",
|
|
221
|
-
properties: {
|
|
222
|
-
prop1: ["value1", "value2"],
|
|
223
|
-
prop2: ["value3", "value4"]
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
const context = createTestContext();
|
|
227
|
-
const result = await makeEvent(context, "known_id", dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), eventConfig, ["anon_id"], ["session_id"]);
|
|
228
|
-
expect(result.prop1 === "value1" || result.prop1 === "value2").toBeTruthy();
|
|
229
|
-
expect(result.prop2 === "value3" || result.prop2 === "value4").toBeTruthy();
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
test('makeFunnel: works', async () => {
|
|
234
|
-
const funnelConfig = {
|
|
235
|
-
sequence: ["step1", "step2"],
|
|
236
|
-
conversionRate: 100,
|
|
237
|
-
order: 'sequential'
|
|
238
|
-
};
|
|
239
|
-
/** @type {Person} */
|
|
240
|
-
const user = { distinct_id: "user1", name: "test", created: dayjs().toISOString(), anonymousIds: [], sessionIds: [] };
|
|
241
|
-
/** @type {UserProfile} */
|
|
242
|
-
const profile = { created: dayjs().toISOString(), distinct_id: "user1" };
|
|
243
|
-
/** @type {Record<string, SCDSchema[]>} */
|
|
244
|
-
const scd = { "scd_example": [{ distinct_id: "user1", insertTime: dayjs().toISOString(), startTime: dayjs().toISOString() }] };
|
|
245
|
-
|
|
246
|
-
const context = createTestContext();
|
|
247
|
-
const [result, converted] = await makeFunnel(context, funnelConfig, user, dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), profile, scd);
|
|
248
|
-
expect(result.length).toBe(2);
|
|
249
|
-
expect(converted).toBe(true);
|
|
250
|
-
expect(result.every(e => validEvent(e))).toBeTruthy();
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test('makeFunnel: conversion rates', async () => {
|
|
254
|
-
const funnelConfig = {
|
|
255
|
-
sequence: ["step1", "step2", "step3"],
|
|
256
|
-
conversionRate: 50,
|
|
257
|
-
order: 'sequential'
|
|
258
|
-
};
|
|
259
|
-
const user = { distinct_id: "user1", name: "test", created: dayjs().toISOString(), anonymousIds: [], sessionIds: [] };
|
|
260
|
-
const profile = { created: dayjs().toISOString(), distinct_id: "user1" };
|
|
261
|
-
const scd = { "scd_example": [{ distinct_id: "user1", insertTime: dayjs().toISOString(), startTime: dayjs().toISOString() }] };
|
|
262
|
-
|
|
263
|
-
const context = createTestContext();
|
|
264
|
-
const [result, converted] = await makeFunnel(context, funnelConfig, user, dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), profile, scd);
|
|
265
|
-
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
266
|
-
expect(result.length).toBeLessThanOrEqual(3);
|
|
267
|
-
expect(result.every(e => validEvent(e))).toBeTruthy();
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test('makeFunnel: ordering', async () => {
|
|
271
|
-
const funnelConfig = {
|
|
272
|
-
sequence: ["step1", "step2", "step3"],
|
|
273
|
-
conversionRate: 100,
|
|
274
|
-
order: 'random'
|
|
275
|
-
};
|
|
276
|
-
const user = { distinct_id: "user1", name: "test", created: dayjs().toISOString(), anonymousIds: [], sessionIds: [] };
|
|
277
|
-
const profile = { created: dayjs().toISOString(), distinct_id: "user1" };
|
|
278
|
-
const scd = { "scd_example": [{ distinct_id: "user1", insertTime: dayjs().toISOString(), startTime: dayjs().toISOString() }] };
|
|
279
|
-
|
|
280
|
-
const context = createTestContext();
|
|
281
|
-
const [result, converted] = await makeFunnel(context, funnelConfig, user, dayjs.unix(global.FIXED_NOW).subtract(30, 'd').unix(), profile, scd);
|
|
282
|
-
expect(result.length).toBe(3);
|
|
283
|
-
expect(converted).toBe(true);
|
|
284
|
-
expect(result.every(e => validEvent(e))).toBeTruthy();
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
test('makeProfile: works', async () => {
|
|
289
|
-
const context = createTestContext();
|
|
290
|
-
const props = {
|
|
291
|
-
name: ["John", "Jane"],
|
|
292
|
-
age: [25, 30]
|
|
293
|
-
};
|
|
294
|
-
const result = await makeProfile(context, props, { foo: "bar" });
|
|
295
|
-
expect(result).toHaveProperty('name');
|
|
296
|
-
expect(result).toHaveProperty('age');
|
|
297
|
-
expect(result).toHaveProperty('foo', 'bar');
|
|
298
|
-
expect(result.name === "John" || result.name === "Jane").toBeTruthy();
|
|
299
|
-
expect(result.age === 25 || result.age === 30).toBeTruthy();
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
test('makeProfile: correct defaults', async () => {
|
|
303
|
-
const context = createTestContext();
|
|
304
|
-
const props = {
|
|
305
|
-
name: ["John", "Jane"],
|
|
306
|
-
age: [25, 30]
|
|
307
|
-
};
|
|
308
|
-
const result = await makeProfile(context, props);
|
|
309
|
-
expect(result).toHaveProperty('name');
|
|
310
|
-
expect(result).toHaveProperty('age');
|
|
311
|
-
expect(result.name === "John" || result.name === "Jane").toBeTruthy();
|
|
312
|
-
expect(result.age === 25 || result.age === 30).toBeTruthy();
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
test('makeSCD: works', async () => {
|
|
317
|
-
const context = createTestContext();
|
|
318
|
-
const result = await makeSCD(context, ["value1", "value2"], "prop1", "distinct_id", 5, dayjs().toISOString());
|
|
319
|
-
expect(result.length).toBeGreaterThan(0);
|
|
320
|
-
const [first, second] = result;
|
|
321
|
-
expect(first).toHaveProperty('prop1');
|
|
322
|
-
expect(second).toHaveProperty('prop1');
|
|
323
|
-
expect(first).toHaveProperty('distinct_id', 'distinct_id');
|
|
324
|
-
expect(second).toHaveProperty('distinct_id', 'distinct_id');
|
|
325
|
-
expect(first).toHaveProperty('startTime');
|
|
326
|
-
expect(second).toHaveProperty('startTime');
|
|
327
|
-
expect(first).toHaveProperty('insertTime');
|
|
328
|
-
expect(second).toHaveProperty('insertTime');
|
|
329
|
-
expect(first.prop1 === "value1" || first.prop1 === "value2").toBeTruthy();
|
|
330
|
-
expect(second.prop1 === "value1" || second.prop1 === "value2").toBeTruthy();
|
|
331
|
-
expect(result[0]).toHaveProperty('distinct_id', 'distinct_id');
|
|
332
|
-
expect(result[0]).toHaveProperty('startTime');
|
|
333
|
-
expect(result[0]).toHaveProperty('insertTime');
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
test('makeSCD: no mutations', async () => {
|
|
337
|
-
const context = createTestContext();
|
|
338
|
-
const result = await makeSCD(context, ["value1", "value2"], "prop1", "distinct_id", 0, dayjs().toISOString());
|
|
339
|
-
expect(result.length).toBe(0);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
test('makeSCD: large mutations', async () => {
|
|
343
|
-
const context = createTestContext();
|
|
344
|
-
const result = await makeSCD(context, ["value1", "value2"], "prop1", "distinct_id", 100, dayjs().subtract(100, 'd').toISOString());
|
|
345
|
-
expect(result.length).toBeGreaterThan(0);
|
|
346
|
-
result.forEach(entry => {
|
|
347
|
-
expect(entry).toHaveProperty('prop1');
|
|
348
|
-
expect(entry).toHaveProperty('distinct_id', 'distinct_id');
|
|
349
|
-
expect(entry).toHaveProperty('startTime');
|
|
350
|
-
expect(entry).toHaveProperty('insertTime');
|
|
351
|
-
expect(entry.prop1 === "value1" || entry.prop1 === "value2").toBeTruthy();
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
test('mirror: create', async () => {
|
|
356
|
-
/** @type {EventSchema} */
|
|
357
|
-
const oldEvent = {
|
|
358
|
-
event: "old",
|
|
359
|
-
insert_id: "test",
|
|
360
|
-
source: "test",
|
|
361
|
-
time: dayjs().toISOString(),
|
|
362
|
-
user_id: "test"
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
/** @type {Config} */
|
|
366
|
-
const config = {
|
|
367
|
-
mirrorProps: {
|
|
368
|
-
"newProp": {
|
|
369
|
-
events: "*",
|
|
370
|
-
strategy: "create",
|
|
371
|
-
values: ["new"]
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
await STORAGE.eventData.hookPush(oldEvent);
|
|
376
|
-
//ugh side fx
|
|
377
|
-
// Create context with the test config and storage
|
|
378
|
-
const context = createTestContext(config);
|
|
379
|
-
context.setStorage(STORAGE);
|
|
380
|
-
await makeMirror(context);
|
|
381
|
-
const [newData] = STORAGE.mirrorEventData;
|
|
382
|
-
expect(newData).toHaveProperty('newProp', "new");
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
test('mirror: delete', async () => {
|
|
386
|
-
/** @type {EventSchema} */
|
|
387
|
-
const oldEvent = {
|
|
388
|
-
event: "old",
|
|
389
|
-
insert_id: "test",
|
|
390
|
-
source: "test",
|
|
391
|
-
time: dayjs().toISOString(),
|
|
392
|
-
user_id: "test",
|
|
393
|
-
oldProp: "valueToDelete"
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
/** @type {Config} */
|
|
397
|
-
const config = {
|
|
398
|
-
mirrorProps: {
|
|
399
|
-
"oldProp": {
|
|
400
|
-
events: "*",
|
|
401
|
-
strategy: "delete"
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
await STORAGE.eventData.hookPush(oldEvent);
|
|
406
|
-
|
|
407
|
-
// Create context with the test config and storage
|
|
408
|
-
const context = createTestContext(config);
|
|
409
|
-
context.setStorage(STORAGE);
|
|
410
|
-
await makeMirror(context);
|
|
411
|
-
const [newData] = STORAGE.mirrorEventData;
|
|
412
|
-
expect(newData).not.toHaveProperty('oldProp');
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
test('mirror: fill', async () => {
|
|
416
|
-
/** @type {EventSchema} */
|
|
417
|
-
const oldEvent = {
|
|
418
|
-
event: "old",
|
|
419
|
-
insert_id: "test",
|
|
420
|
-
source: "test",
|
|
421
|
-
time: dayjs().subtract(8, 'days').toISOString(), // Set time to 8 days ago
|
|
422
|
-
user_id: "test",
|
|
423
|
-
fillProp: "initialValue"
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
/** @type {Config} */
|
|
427
|
-
const config = {
|
|
428
|
-
mirrorProps: {
|
|
429
|
-
"fillProp": {
|
|
430
|
-
events: "*",
|
|
431
|
-
strategy: "fill",
|
|
432
|
-
values: ["filledValue"],
|
|
433
|
-
daysUnfilled: 7
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
};
|
|
437
|
-
await STORAGE.eventData.hookPush(oldEvent);
|
|
438
|
-
|
|
439
|
-
// Create context with the test config and storage
|
|
440
|
-
const context = createTestContext(config);
|
|
441
|
-
context.setStorage(STORAGE);
|
|
442
|
-
await makeMirror(context);
|
|
443
|
-
const [newData] = STORAGE.mirrorEventData;
|
|
444
|
-
expect(newData).toHaveProperty('fillProp', "filledValue");
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
test('mirror: update', async () => {
|
|
448
|
-
/** @type {EventSchema} */
|
|
449
|
-
const oldEvent = {
|
|
450
|
-
event: "old",
|
|
451
|
-
insert_id: "test",
|
|
452
|
-
source: "test",
|
|
453
|
-
time: dayjs().toISOString(),
|
|
454
|
-
user_id: "test",
|
|
455
|
-
updateProp: "initialValue"
|
|
456
|
-
};
|
|
457
|
-
|
|
458
|
-
/** @type {Config} */
|
|
459
|
-
const config = {
|
|
460
|
-
mirrorProps: {
|
|
461
|
-
"updateProp": {
|
|
462
|
-
events: "*",
|
|
463
|
-
strategy: "update",
|
|
464
|
-
values: ["updatedValue"]
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
await STORAGE.eventData.hookPush(oldEvent);
|
|
469
|
-
|
|
470
|
-
// Create context with the test config and storage
|
|
471
|
-
const context = createTestContext(config);
|
|
472
|
-
context.setStorage(STORAGE);
|
|
473
|
-
await makeMirror(context);
|
|
474
|
-
const [newData] = STORAGE.mirrorEventData;
|
|
475
|
-
expect(newData).toHaveProperty('updateProp', "initialValue");
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
test('mirror: update nulls', async () => {
|
|
479
|
-
/** @type {EventSchema} */
|
|
480
|
-
const oldEvent = {
|
|
481
|
-
event: "old",
|
|
482
|
-
insert_id: "test",
|
|
483
|
-
source: "test",
|
|
484
|
-
time: dayjs().toISOString(),
|
|
485
|
-
user_id: "test"
|
|
486
|
-
// updateProp is not set initially
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
/** @type {Config} */
|
|
490
|
-
const config = {
|
|
491
|
-
mirrorProps: {
|
|
492
|
-
"updateProp": {
|
|
493
|
-
events: "*",
|
|
494
|
-
strategy: "update",
|
|
495
|
-
values: ["updatedValue"]
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
await STORAGE.eventData.hookPush(oldEvent);
|
|
500
|
-
|
|
501
|
-
// Create context with the test config and storage
|
|
502
|
-
const context = createTestContext(config);
|
|
503
|
-
context.setStorage(STORAGE);
|
|
504
|
-
await makeMirror(context);
|
|
505
|
-
const [newData] = STORAGE.mirrorEventData;
|
|
506
|
-
expect(newData).toHaveProperty('updateProp', "updatedValue");
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
test('mirror: update with no initial value', async () => {
|
|
511
|
-
/** @type {EventSchema} */
|
|
512
|
-
const oldEvent = {
|
|
513
|
-
event: "old",
|
|
514
|
-
insert_id: "test",
|
|
515
|
-
source: "test",
|
|
516
|
-
time: dayjs().toISOString(),
|
|
517
|
-
user_id: "test"
|
|
518
|
-
// updateProp is not set initially
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
/** @type {Config} */
|
|
522
|
-
const config = {
|
|
523
|
-
mirrorProps: {
|
|
524
|
-
"updateProp": {
|
|
525
|
-
events: "*",
|
|
526
|
-
strategy: "update",
|
|
527
|
-
values: ["updatedValue"]
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
await STORAGE.eventData.hookPush(oldEvent);
|
|
532
|
-
|
|
533
|
-
// Create context with the test config and storage
|
|
534
|
-
const context = createTestContext(config);
|
|
535
|
-
context.setStorage(STORAGE);
|
|
536
|
-
await makeMirror(context);
|
|
537
|
-
const [newData] = STORAGE.mirrorEventData;
|
|
538
|
-
expect(newData).toHaveProperty('updateProp', "updatedValue");
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
describe.sequential('orchestrators', () => {
|
|
546
|
-
|
|
547
|
-
test('sendToMixpanel: works', async () => {
|
|
548
|
-
CONFIG.token = "test_token";
|
|
549
|
-
const context = createTestContext(CONFIG);
|
|
550
|
-
context.setStorage(STORAGE);
|
|
551
|
-
const result = await sendToMixpanel(context);
|
|
552
|
-
expect(result).toHaveProperty('events');
|
|
553
|
-
expect(result).toHaveProperty('users');
|
|
554
|
-
expect(result).toHaveProperty('groups');
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
test('sendToMixpanel: no token', async () => {
|
|
558
|
-
CONFIG.token = null;
|
|
559
|
-
const context = createTestContext(CONFIG);
|
|
560
|
-
context.setStorage(STORAGE);
|
|
561
|
-
await expect(sendToMixpanel(context)).rejects.toThrow();
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
test('sendToMixpanel: empty storage', async () => {
|
|
565
|
-
CONFIG.token = "test_token";
|
|
566
|
-
STORAGE = {
|
|
567
|
-
eventData: await hookArray([], {}),
|
|
568
|
-
userProfilesData: await hookArray([], {}),
|
|
569
|
-
adSpendData: await hookArray([], {}),
|
|
570
|
-
scdTableData: [await hookArray([], {})],
|
|
571
|
-
groupProfilesData: await hookArray([], {}),
|
|
572
|
-
lookupTableData: await hookArray([], {}),
|
|
573
|
-
mirrorEventData: await hookArray([], {})
|
|
574
|
-
};
|
|
575
|
-
const context = createTestContext(CONFIG);
|
|
576
|
-
context.setStorage(STORAGE);
|
|
577
|
-
const result = await sendToMixpanel(context);
|
|
578
|
-
expect(result.events.success).toBe(0);
|
|
579
|
-
expect(result.users.success).toBe(0);
|
|
580
|
-
expect(result.groups).toHaveLength(0);
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
test('userLoop: works (no funnels; no inference)', async () => {
|
|
585
|
-
/** @type {Config} */
|
|
586
|
-
const config = {
|
|
587
|
-
numUsers: 2,
|
|
588
|
-
numEvents: 40,
|
|
589
|
-
numDays: 30,
|
|
590
|
-
userProps: {},
|
|
591
|
-
scdProps: {},
|
|
592
|
-
funnels: [],
|
|
593
|
-
isAnonymous: false,
|
|
594
|
-
hasAnonIds: false,
|
|
595
|
-
hasSessionIds: false,
|
|
596
|
-
hasLocation: false,
|
|
597
|
-
alsoInferFunnels: false,
|
|
598
|
-
events: [{ event: "foo" }, { event: "bar" }, { event: "baz" }]
|
|
599
|
-
};
|
|
600
|
-
const context = createTestContext(config);
|
|
601
|
-
context.setStorage(STORAGE);
|
|
602
|
-
await userLoop(context);
|
|
603
|
-
expect(STORAGE.userProfilesData.length).toBe(2);
|
|
604
|
-
expect(STORAGE.eventData.length).toBeGreaterThan(15);
|
|
605
|
-
expect(STORAGE.eventData.every(e => validEvent(e))).toBeTruthy();
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
test('userLoop: works (funnels)', async () => {
|
|
610
|
-
/** @type {Config} */
|
|
611
|
-
const config = {
|
|
612
|
-
numUsers: 2,
|
|
613
|
-
numEvents: 50,
|
|
614
|
-
numDays: 30,
|
|
615
|
-
userProps: {},
|
|
616
|
-
scdProps: {},
|
|
617
|
-
events: [],
|
|
618
|
-
funnels: [{ sequence: ["step1", "step2"], conversionRate: 100, order: 'sequential' }],
|
|
619
|
-
};
|
|
620
|
-
const context = createTestContext(config);
|
|
621
|
-
context.setStorage(STORAGE);
|
|
622
|
-
await userLoop(context);
|
|
623
|
-
expect(STORAGE.userProfilesData.length).toBe(2);
|
|
624
|
-
expect(STORAGE.eventData.length).toBeGreaterThan(15);
|
|
625
|
-
expect(STORAGE.eventData.every(e => validEvent(e))).toBeTruthy();
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
test('userLoop: mixed config', async () => {
|
|
631
|
-
const config = {
|
|
632
|
-
numUsers: 3,
|
|
633
|
-
numEvents: 15,
|
|
634
|
-
numDays: 10,
|
|
635
|
-
userProps: { name: ["Alice", "Bob", "Charlie"] },
|
|
636
|
-
scdProps: { prop1: ["value1", "value2"] },
|
|
637
|
-
funnels: [],
|
|
638
|
-
events: [{ event: "event1" }, { event: "event2" }]
|
|
639
|
-
|
|
640
|
-
};
|
|
641
|
-
const context = createTestContext(config);
|
|
642
|
-
context.setStorage(STORAGE);
|
|
643
|
-
await userLoop(context);
|
|
644
|
-
expect(STORAGE.userProfilesData.length).toBe(3);
|
|
645
|
-
expect(STORAGE.eventData.length).toBeGreaterThan(5);
|
|
646
|
-
expect(STORAGE.scdTableData[0].length).toBeGreaterThan(5);
|
|
647
|
-
expect(STORAGE.eventData.every(e => validEvent(e))).toBeTruthy();
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
test('userLoop: no events', async () => {
|
|
651
|
-
const config = {
|
|
652
|
-
numUsers: 2,
|
|
653
|
-
numEvents: 0,
|
|
654
|
-
numDays: 30,
|
|
655
|
-
userProps: {},
|
|
656
|
-
scdProps: {},
|
|
657
|
-
funnels: [],
|
|
658
|
-
isAnonymous: false,
|
|
659
|
-
hasAnonIds: false,
|
|
660
|
-
hasSessionIds: false,
|
|
661
|
-
hasLocation: false,
|
|
662
|
-
events: []
|
|
663
|
-
};
|
|
664
|
-
const context = createTestContext(config);
|
|
665
|
-
context.setStorage(STORAGE);
|
|
666
|
-
await userLoop(context);
|
|
667
|
-
expect(STORAGE.userProfilesData.length).toBe(2);
|
|
668
|
-
expect(STORAGE.eventData.length).toBe(0);
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
test('validateDungeonConfig: works', async () => {
|
|
674
|
-
const config = {
|
|
675
|
-
numEvents: 100,
|
|
676
|
-
numUsers: 10,
|
|
677
|
-
numDays: 30
|
|
678
|
-
};
|
|
679
|
-
const result = await validateDungeonConfig(config);
|
|
680
|
-
expect(result).toHaveProperty('numEvents', 100);
|
|
681
|
-
expect(result).toHaveProperty('numUsers', 10);
|
|
682
|
-
expect(result).toHaveProperty('numDays', 30);
|
|
683
|
-
expect(result).toHaveProperty('events');
|
|
684
|
-
expect(result).toHaveProperty('superProps');
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
test('validateDungeonConfig: correct defaults', async () => {
|
|
688
|
-
const config = {};
|
|
689
|
-
const result = await validateDungeonConfig(config);
|
|
690
|
-
expect(result).toHaveProperty('numEvents', 100_000);
|
|
691
|
-
expect(result).toHaveProperty('numUsers', 1000);
|
|
692
|
-
expect(result).toHaveProperty('numDays', 30);
|
|
693
|
-
expect(result).toHaveProperty('events');
|
|
694
|
-
expect(result).toHaveProperty('superProps');
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
test('validateDungeonConfig: merges', async () => {
|
|
698
|
-
const config = {
|
|
699
|
-
numEvents: 100,
|
|
700
|
-
numUsers: 10,
|
|
701
|
-
numDays: 30,
|
|
702
|
-
events: [{ event: "test_event" }],
|
|
703
|
-
superProps: { luckyNumber: [7] }
|
|
704
|
-
};
|
|
705
|
-
const result = await validateDungeonConfig(config);
|
|
706
|
-
expect(result).toHaveProperty('numEvents', 100);
|
|
707
|
-
expect(result).toHaveProperty('numUsers', 10);
|
|
708
|
-
expect(result).toHaveProperty('numDays', 30);
|
|
709
|
-
expect(result).toHaveProperty('events', [{ event: "test_event" }]);
|
|
710
|
-
expect(result).toHaveProperty('superProps', { luckyNumber: [7] });
|
|
711
|
-
});
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
});
|