make-mp-data 1.1.18 → 1.2.0

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.
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "cSpell.words": [
3
+ "timesoup",
3
4
  "unparse"
4
5
  ],
5
6
  "jest.runMode": "on-demand",
package/cli.js CHANGED
@@ -35,7 +35,7 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
35
35
  type: 'string'
36
36
  })
37
37
  .option("seed", {
38
- demandOption: false,
38
+ demandOption: false,
39
39
  alias: 's',
40
40
  describe: 'randomness seed; used to create distinct_ids',
41
41
  type: 'string'
@@ -72,6 +72,24 @@ DATA MODEL: https://github.com/ak--47/make-mp-data/blob/main/default.js
72
72
  describe: 'either US or EU',
73
73
  type: 'string'
74
74
  })
75
+ .options("complex", {
76
+ demandOption: false,
77
+ default: false,
78
+ describe: 'use complex data model (model all entities)',
79
+ alias: 'c',
80
+ type: 'boolean',
81
+ coerce: (value) => {
82
+ if (typeof value === 'boolean') return value;
83
+ if (value === 'true') {
84
+ return true;
85
+ }
86
+ if (value === 'false') {
87
+ return false;
88
+ }
89
+
90
+ return true;
91
+ }
92
+ })
75
93
  .option("writeToDisk", {
76
94
  demandOption: false,
77
95
  default: true,
package/index.js CHANGED
@@ -14,50 +14,19 @@ const Chance = require("chance");
14
14
  const chance = new Chance();
15
15
  const { touch, comma, bytesHuman, mkdir } = require("ak-tools");
16
16
  const Papa = require("papaparse");
17
- const {
18
- weightedRange,
19
- pick,
20
- day,
21
- integer,
22
- makeProducts,
23
- date,
24
- progress,
25
- person,
26
- choose,
27
- range,
28
- exhaust,
29
- openFinder,
30
- applySkew,
31
- boxMullerRandom,
32
- getUniqueKeys
33
- } = require("./utils.js");
17
+ const u = require("./utils.js");
18
+ const AKsTimeSoup = require("./timesoup.js");
34
19
  const dayjs = require("dayjs");
35
20
  const utc = require("dayjs/plugin/utc");
36
21
  dayjs.extend(utc);
37
22
  const cliParams = require("./cli.js");
38
23
  const { makeName, md5 } = require('ak-tools');
39
-
40
- Array.prototype.pickOne = pick;
41
24
  const NOW = dayjs().unix();
42
25
  let VERBOSE = false;
43
26
 
44
27
  /** @typedef {import('./types.d.ts').Config} Config */
45
28
  /** @typedef {import('./types.d.ts').EventConfig} EventConfig */
46
29
 
47
-
48
- const PEAK_DAYS = [
49
- dayjs().subtract(2, "day").unix(),
50
- dayjs().subtract(3, "day").unix(),
51
- dayjs().subtract(5, "day").unix(),
52
- dayjs().subtract(7, "day").unix(),
53
- dayjs().subtract(11, "day").unix(),
54
- dayjs().subtract(13, "day").unix(),
55
- dayjs().subtract(17, "day").unix(),
56
- dayjs().subtract(19, "day").unix(),
57
- dayjs().subtract(23, "day").unix(),
58
- dayjs().subtract(29, "day").unix(),
59
- ];
60
-
61
30
  /**
62
31
  * generates fake mixpanel data
63
32
  * @param {Config} config
@@ -74,7 +43,7 @@ async function main(config) {
74
43
  favoriteColor: ["red", "green", "blue", "yellow"],
75
44
  spiritAnimal: chance.animal,
76
45
  },
77
- scdProps = { NPS: weightedRange(0, 10, 150, 1.6) },
46
+ scdProps = { NPS: u.weightedRange(0, 10, 150, 1.6) },
78
47
  groupKeys = [],
79
48
  groupProps = {},
80
49
  lookupTables = [],
@@ -88,7 +57,7 @@ async function main(config) {
88
57
  } = config;
89
58
  VERBOSE = verbose;
90
59
  config.simulationName = makeName();
91
- global.config = config;
60
+ global.MP_SIMULATION_CONFIG = config;
92
61
  const uuidChance = new Chance(seed);
93
62
  log(`------------------SETUP------------------`);
94
63
  log(`\nyour data simulation will heretofore be known as: \n\n\t${config.simulationName.toUpperCase()}...\n`);
@@ -99,18 +68,18 @@ async function main(config) {
99
68
  //the function which generates $distinct_id + $anonymous_ids, $session_ids, and $created, skewing towards the present
100
69
  function generateUser() {
101
70
  const distinct_id = uuidChance.guid();
102
- let z = boxMullerRandom();
71
+ let z = u.boxMullerRandom();
103
72
  const skew = chance.normal({ mean: 10, dev: 3 });
104
- z = applySkew(z, skew);
73
+ z = u.applySkew(z, skew);
105
74
 
106
75
  // Scale and shift the normally distributed value to fit the range of days
107
- const maxZ = integer(2, 4);
76
+ const maxZ = u.integer(2, 4);
108
77
  const scaledZ = (z / maxZ + 1) / 2;
109
78
  const daysAgoBorn = Math.round(scaledZ * (numDays - 1)) + 1;
110
79
 
111
80
  return {
112
81
  distinct_id,
113
- ...person(daysAgoBorn),
82
+ ...u.person(daysAgoBorn),
114
83
  };
115
84
  }
116
85
 
@@ -136,16 +105,16 @@ async function main(config) {
136
105
  const avgEvPerUser = Math.floor(numEvents / numUsers);
137
106
 
138
107
  //user loop
139
- log(`---------------SIMULATION----------------`, `\n\n`);
108
+ log(`---------------SIMULATION----------------`, "\n\n");
140
109
  for (let i = 1; i < numUsers + 1; i++) {
141
- progress("users", i);
110
+ u.progress("users", i);
142
111
  const user = generateUser();
143
112
  const { distinct_id, $created, anonymousIds, sessionIds } = user;
144
113
  userProfilesData.push(makeProfile(userProps, user));
145
114
  const mutations = chance.integer({ min: 1, max: 10 });
146
115
  scdTableData.push(makeSCD(scdProps, distinct_id, mutations, $created));
147
116
  const numEventsThisUser = Math.round(
148
- chance.normal({ mean: avgEvPerUser, dev: avgEvPerUser / integer(3, 7) })
117
+ chance.normal({ mean: avgEvPerUser, dev: avgEvPerUser / u.integer(3, 7) })
149
118
  );
150
119
 
151
120
  if (firstEvents.length) {
@@ -189,7 +158,7 @@ async function main(config) {
189
158
  const groupCardinality = groupPair[1];
190
159
  const groupProfiles = [];
191
160
  for (let i = 1; i < groupCardinality + 1; i++) {
192
- progress("groups", i);
161
+ u.progress("groups", i);
193
162
  const group = {
194
163
  [groupKey]: i,
195
164
  ...makeProfile(groupProps[groupKey]),
@@ -206,7 +175,7 @@ async function main(config) {
206
175
  const { key, entries, attributes } = lookupTable;
207
176
  const data = [];
208
177
  for (let i = 1; i < entries + 1; i++) {
209
- progress("lookups", i);
178
+ u.progress("lookups", i);
210
179
  const item = {
211
180
  [key]: i,
212
181
  ...makeProfile(attributes),
@@ -238,17 +207,19 @@ async function main(config) {
238
207
  log(`-----------------WRITES------------------`, `\n\n`);
239
208
  //write the files
240
209
  if (writeToDisk) {
241
- if (verbose) log(`writing files... for ${config.simulationName}`);
242
- for (const pair of pairs) {
210
+ if (verbose) log(`writing files... for ${config.simulationName}\n`);
211
+ loopFiles: for (const pair of pairs) {
243
212
  const [paths, data] = pair;
213
+ if (!data.length) continue loopFiles;
244
214
  for (const path of paths) {
245
215
  let datasetsToWrite;
246
216
  if (data?.[0]?.["key"]) datasetsToWrite = data.map((d) => d.data);
247
217
  else datasetsToWrite = [data];
248
218
  for (const writeData of datasetsToWrite) {
249
- if (format === "csv") {
219
+ //if it's a lookup table, it's always a CSV
220
+ if (format === "csv" || path.includes("-LOOKUP.csv")) {
250
221
  log(`writing ${path}`);
251
- const columns = getUniqueKeys(writeData);
222
+ const columns = u.getUniqueKeys(writeData);
252
223
  //papa parse needs nested JSON stringified
253
224
  writeData.forEach((e) => {
254
225
  for (const key in e) {
@@ -329,6 +300,9 @@ async function main(config) {
329
300
  };
330
301
  }
331
302
 
303
+
304
+
305
+
332
306
  function makeProfile(props, defaults) {
333
307
  //build the spec
334
308
  const profile = {
@@ -347,6 +321,7 @@ function makeProfile(props, defaults) {
347
321
  }
348
322
 
349
323
  function makeSCD(props, distinct_id, mutations, $created) {
324
+ if (JSON.stringify(props) === "{}") return [];
350
325
  const scdEntries = [];
351
326
  let lastInserted = dayjs($created);
352
327
  const deltaDays = dayjs().diff(lastInserted, "day");
@@ -355,12 +330,12 @@ function makeSCD(props, distinct_id, mutations, $created) {
355
330
  if (lastInserted.isAfter(dayjs())) break;
356
331
  const scd = makeProfile(props, { distinct_id });
357
332
  scd.startTime = lastInserted.toISOString();
358
- lastInserted = lastInserted.add(integer(1, 1000), "seconds");
333
+ lastInserted = lastInserted.add(u.integer(1, 1000), "seconds");
359
334
  scd.insertTime = lastInserted.toISOString();
360
335
  scdEntries.push({ ...scd });
361
336
  lastInserted = lastInserted
362
- .add(integer(0, deltaDays), "day")
363
- .subtract(integer(1, 1000), "seconds");
337
+ .add(u.integer(0, deltaDays), "day")
338
+ .subtract(u.integer(1, 1000), "seconds");
364
339
  }
365
340
 
366
341
  return scdEntries;
@@ -378,7 +353,7 @@ function makeSCD(props, distinct_id, mutations, $created) {
378
353
  * @param {Boolean} isFirstEvent=false
379
354
  */
380
355
  function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events, superProps, groupKeys, isFirstEvent = false) {
381
- let chosenEvent = events.pickOne();
356
+ let chosenEvent = chance.pickone(events);
382
357
 
383
358
  //allow for a string shorthand
384
359
  if (typeof chosenEvent === "string") {
@@ -393,11 +368,11 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
393
368
 
394
369
  //event time
395
370
  if (isFirstEvent) event.time = dayjs.unix(earliestTime).toISOString();
396
- if (!isFirstEvent) event.time = AKsTimeSoup(earliestTime, NOW, PEAK_DAYS);
371
+ if (!isFirstEvent) event.time = AKsTimeSoup(earliestTime, NOW);
397
372
 
398
373
  // anonymous and session ids
399
- if (global?.config.anonIds) event.$device_id = chance.pickone(anonymousIds);
400
- if (global?.config.sessionIds) event.$session_id = chance.pickone(sessionIds);
374
+ if (global.MP_SIMULATION_CONFIG?.anonIds) event.$device_id = chance.pickone(anonymousIds);
375
+ if (global.MP_SIMULATION_CONFIG?.sessionIds) event.$session_id = chance.pickone(sessionIds);
401
376
 
402
377
  //sometimes have a $user_id
403
378
  if (!isFirstEvent && chance.bool({ likelihood: 42 })) event.$user_id = distinct_id;
@@ -410,7 +385,7 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
410
385
  //iterate through custom properties
411
386
  for (const key in props) {
412
387
  try {
413
- event[key] = choose(props[key]);
388
+ event[key] = u.choose(props[key]);
414
389
  } catch (e) {
415
390
  console.error(`error with ${key} in ${chosenEvent.event} event`, e);
416
391
  debugger;
@@ -422,7 +397,7 @@ function makeEvent(distinct_id, anonymousIds, sessionIds, earliestTime, events,
422
397
  const groupKey = groupPair[0];
423
398
  const groupCardinality = groupPair[1];
424
399
 
425
- event[groupKey] = weightedRange(1, groupCardinality).pickOne();
400
+ event[groupKey] = u.pick(u.weightedRange(1, groupCardinality));
426
401
  }
427
402
 
428
403
  //make $insert_id
@@ -440,9 +415,9 @@ function buildFileNames(config) {
440
415
  if (config.writeToDisk) writeDir = mkdir("./data");
441
416
 
442
417
  const writePaths = {
443
- eventFiles: [path.join(writeDir, `events-${simName}.${extension}`)],
444
- userFiles: [path.join(writeDir, `users-${simName}.${extension}`)],
445
- scdFiles: [path.join(writeDir, `scd-${simName}.${extension}`)],
418
+ eventFiles: [path.join(writeDir, `${simName}-EVENTS.${extension}`)],
419
+ userFiles: [path.join(writeDir, `${simName}-USERS.${extension}`)],
420
+ scdFiles: [path.join(writeDir, `${simName}-SCD.${extension}`)],
446
421
  groupFiles: [],
447
422
  lookupFiles: [],
448
423
  folder: writeDir,
@@ -451,88 +426,28 @@ function buildFileNames(config) {
451
426
  for (const groupPair of groupKeys) {
452
427
  const groupKey = groupPair[0];
453
428
  writePaths.groupFiles.push(
454
- path.join(writeDir, `group-${groupKey}-${simName}.${extension}`)
429
+ path.join(writeDir, `${simName}-${groupKey}-GROUP.${extension}`)
455
430
  );
456
431
  }
457
432
 
458
433
  for (const lookupTable of lookupTables) {
459
434
  const { key } = lookupTable;
460
435
  writePaths.lookupFiles.push(
461
- path.join(writeDir, `lookup-${key}-${simName}.${extension}`)
436
+ //lookups are always CSVs
437
+ path.join(writeDir, `${simName}-${key}-LOOKUP.csv`)
462
438
  );
463
439
  }
464
440
 
465
441
  return writePaths;
466
442
  }
467
443
 
468
- /**
469
- * essentially, a timestamp generator with a twist
470
- * @param {number} earliestTime - The earliest timestamp in Unix format.
471
- * @param {number} latestTime - The latest timestamp in Unix format.
472
- * @param {Array} peakDays - Array of Unix timestamps representing the start of peak days.
473
- * @returns {number} - The generated event timestamp in Unix format.
474
- */
475
- function AKsTimeSoup(earliestTime, latestTime = NOW, peakDays = PEAK_DAYS) {
476
- let chosenTime;
477
- let eventTime;
478
- let validTime = false;
479
-
480
- if (typeof earliestTime !== "number") {
481
- if (parseInt(earliestTime) > 0) earliestTime = parseInt(earliestTime);
482
- if (dayjs(earliestTime).isValid()) earliestTime = dayjs(earliestTime).unix();
483
- }
484
-
485
- while (!validTime) {
486
-
487
- // Define business hours
488
- const peakStartHour = 4; // 4 AM
489
- const peakEndHour = 23; // 11 PM
490
- const likelihoodOfPeakDay = chance.integer({ min: integer(5, 42), max: integer(43, 69) }); // Randomize likelihood with CHAOS!~~
491
-
492
- // Select a day, with a preference for peak days
493
- let selectedDay;
494
- if (chance.bool({ likelihood: likelihoodOfPeakDay })) { // Randomized likelihood to pick a peak day
495
- selectedDay = peakDays.length > 0 ? chance.pickone(peakDays) : integer(earliestTime, latestTime);
496
- } else {
497
- // Introduce minor peaks by allowing some events to still occur during business hours
498
- selectedDay = chance.bool({ likelihood: integer(1, 42) })
499
- ? chance.pickone(peakDays)
500
- : integer(earliestTime, latestTime);
501
- }
502
-
503
- // Normalize selectedDay to the start of the day
504
- selectedDay = dayjs.unix(selectedDay).startOf('day').unix();
505
-
506
- // Generate a random time within business hours with a higher concentration in the middle of the period
507
- const businessStart = dayjs.unix(selectedDay).hour(peakStartHour).minute(0).second(0).unix();
508
- const businessEnd = dayjs.unix(selectedDay).hour(peakEndHour).minute(0).second(0).unix();
509
-
510
- if (selectedDay === peakDays[0]) {
511
- // Use a skewed distribution for peak days
512
- eventTime = chance.normal({ mean: (businessEnd + businessStart) / integer(1, 4), dev: (businessEnd - businessStart) / integer(2, 8) });
513
- } else {
514
- // For non-peak days, use a uniform distribution to add noise
515
- eventTime = integer(integer(businessStart, businessEnd), integer(businessStart, businessEnd));
516
- }
517
-
518
- // usually, ensure the event time is within business hours
519
- if (chance.bool({ likelihood: 42 })) eventTime = Math.min(Math.max(eventTime, businessStart), businessEnd);
520
-
521
- if (eventTime > 0) validTime = true;
522
- const parsedTime = dayjs.unix(eventTime).toISOString();
523
- if (!parsedTime.startsWith('20')) validTime = false;
524
-
525
- }
526
- chosenTime = dayjs.unix(eventTime).toISOString();
527
- return chosenTime;
528
- }
529
444
 
530
445
 
531
446
 
532
447
  // this is for CLI
533
448
  if (require.main === module) {
534
449
  const args = cliParams();
535
- const { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk } = args;
450
+ const { token, seed, format, numDays, numUsers, numEvents, region, writeToDisk, complex = false } = args;
536
451
  const suppliedConfig = args._[0];
537
452
 
538
453
  //if the user specifics an separate config file
@@ -540,9 +455,18 @@ if (require.main === module) {
540
455
  if (suppliedConfig) {
541
456
  log(`using ${suppliedConfig} for data\n`);
542
457
  config = require(path.resolve(suppliedConfig));
543
- } else {
544
- log(`... using default configuration ...\n`);
545
- config = require("./default.js");
458
+ }
459
+ else {
460
+ if (complex) {
461
+ log(`... using default COMPLEX configuration [everything] ...\n`);
462
+ log(`... for more simple data, don't use the --complex flag ...\n`);
463
+ config = require("./examples/complex.js");
464
+ }
465
+ else {
466
+ log(`... using default SIMPLE configuration [events + users] ...\n`);
467
+ log(`... for more complex data, use the --complex flag ...\n`);
468
+ config = require("./examples/simple.js");
469
+ }
546
470
  }
547
471
 
548
472
  //override config with cli params
@@ -589,9 +513,10 @@ if (require.main === module) {
589
513
  })
590
514
  .finally(() => {
591
515
  log("have a wonderful day :)");
592
- openFinder(path.resolve("./data"));
516
+ u.openFinder(path.resolve("./data"));
593
517
  });
594
518
  } else {
519
+ main.utils = { ...u };
595
520
  main.timeSoup = AKsTimeSoup;
596
521
  module.exports = main;
597
522
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "make-mp-data",
3
- "version": "1.1.18",
3
+ "version": "1.2.0",
4
4
  "description": "builds all mixpanel primitives for a given project",
5
5
  "main": "index.js",
6
6
  "types": "types.d.ts",
@@ -9,7 +9,7 @@
9
9
  "prune": "rm ./data/*",
10
10
  "go": "sh ./scripts/go.sh",
11
11
  "post": "npm publish",
12
- "test": "jest",
12
+ "test": "jest --runInBand",
13
13
  "deps": "sh ./scripts/deps.sh"
14
14
  },
15
15
  "repository": {
package/tests/e2e.test.js CHANGED
@@ -8,55 +8,94 @@ require('dotenv').config();
8
8
  const { execSync } = require("child_process");
9
9
  const u = require('ak-tools');
10
10
 
11
+ const simple = require('../examples/simple');
12
+ const complex = require('../examples/complex');
13
+ const deep = require('../examples/deepNest');
14
+
11
15
  const timeout = 60000;
12
16
  const testToken = process.env.TEST_TOKEN;
13
17
 
18
+ describe('module', () => {
14
19
 
15
- describe('e2e', () => {
16
-
17
- test('works as module', async () => {
20
+ test('works as module (no config)', async () => {
18
21
  console.log('MODULE TEST');
19
- const results = await generate({ writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
22
+ const results = await generate({ verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
20
23
  const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
21
24
  expect(eventData.length).toBeGreaterThan(980);
22
25
  expect(groupProfilesData.length).toBe(0);
23
26
  expect(lookupTableData.length).toBe(0);
24
27
  expect(scdTableData.length).toBeGreaterThan(200);
28
+ expect(userProfilesData.length).toBe(100);
29
+
30
+ }, timeout);
31
+
32
+ test('works as module (simple)', async () => {
33
+ console.log('MODULE TEST: SIMPLE');
34
+ const results = await generate({ ...simple, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
35
+ const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
36
+ expect(eventData.length).toBeGreaterThan(980);
37
+ expect(groupProfilesData.length).toBe(0);
38
+ expect(lookupTableData.length).toBe(0);
39
+ expect(scdTableData.length).toBe(0);
25
40
  expect(userProfilesData.length).toBe(100);
26
41
 
27
42
  }, timeout);
28
43
 
29
- test('works as CLI', async () => {
30
- console.log('CLI TEST');
31
- const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it"`);
32
- expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
33
- const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
34
- expect(csvs.length).toBe(5);
44
+ test('works as module (complex)', async () => {
45
+ console.log('MODULE TEST: COMPLEX');
46
+ const results = await generate({ ...complex, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
47
+ const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
48
+ expect(eventData.length).toBeGreaterThan(980);
49
+ expect(groupProfilesData[0]?.data?.length).toBe(350);
50
+ expect(lookupTableData.length).toBe(1);
51
+ expect(lookupTableData[0].data.length).toBe(1000);
52
+ expect(scdTableData.length).toBeGreaterThan(200);
53
+ expect(userProfilesData.length).toBe(100);
54
+
35
55
  }, timeout);
36
56
 
37
- test('sends data to mixpanel', async () => {
38
- console.log('NETWORK TEST');
39
- const results = await generate({ writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it", token: testToken });
40
- const { events, users, groups } = results.import;
41
- expect(events.success).toBeGreaterThan(980);
42
- expect(users.success).toBe(100);
43
- expect(groups.length).toBe(0);
57
+ test('works as module (deep nest)', async () => {
58
+ console.log('MODULE TEST: DEEP NEST');
59
+ const results = await generate({ ...deep, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
60
+ const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
61
+ expect(eventData.length).toBeGreaterThan(980);
62
+ expect(groupProfilesData.length).toBe(0);
63
+ expect(lookupTableData.length).toBe(0);
64
+ expect(scdTableData.length).toBeGreaterThan(200);
65
+ expect(userProfilesData.length).toBe(100);
66
+
44
67
  }, timeout);
45
68
 
46
- test('every record is valid', async () => {
47
- console.log('VALIDATION TEST');
48
- const results = await generate({ writeToDisk: false, numEvents: 10000, numUsers: 500 });
49
- const { eventData, userProfilesData } = results;
50
- const areEventsValid = eventData.every(validateEvent);
51
- const areUsersValid = userProfilesData.every(validateUser);
52
69
 
53
- const invalidEvents = eventData.filter(e => !validateEvent(e));
54
- const invalidUsers = userProfilesData.filter(u => !validateUser(u));
70
+ });
55
71
 
56
- expect(areEventsValid).toBe(true);
57
- expect(areUsersValid).toBe(true);
72
+ describe('cli', () => {
73
+ test('works as CLI (complex)', async () => {
74
+ console.log('COMPLEX CLI TEST');
75
+ const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it" --complex`);
76
+ expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
77
+ const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
78
+ expect(csvs.length).toBe(5);
79
+ clearData();
58
80
  }, timeout);
59
81
 
82
+ test('works as CLI (simple)', async () => {
83
+ console.log('simple CLI TEST');
84
+ const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it"`);
85
+ expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
86
+ const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
87
+ expect(csvs.length).toBe(2);
88
+ clearData();
89
+ }, timeout);
90
+
91
+ test('works as CLI (custom)', async () => {
92
+ console.log('custom CLI TEST');
93
+ const run = execSync(`node ./index.js ./examples/deepNest.js`);
94
+ expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
95
+ const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
96
+ expect(csvs.length).toBe(3);
97
+ clearData();
98
+ }, timeout);
60
99
 
61
100
  });
62
101
 
@@ -91,16 +130,47 @@ describe('options + tweaks', () => {
91
130
  expect(anonIds.length).toBe(0);
92
131
  }, timeout);
93
132
 
133
+ test('sends data to mixpanel', async () => {
134
+ console.log('NETWORK TEST');
135
+ const results = await generate({ verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it", token: testToken });
136
+ const { events, users, groups } = results.import;
137
+ expect(events.success).toBeGreaterThan(980);
138
+ expect(users.success).toBe(100);
139
+ expect(groups.length).toBe(0);
140
+ }, timeout);
94
141
 
95
- });
142
+ test('every record is valid', async () => {
143
+ console.log('VALIDATION TEST');
144
+ const results = await generate({ verbose: true, writeToDisk: false, numEvents: 10000, numUsers: 500 });
145
+ const { eventData, userProfilesData } = results;
146
+ const areEventsValid = eventData.every(validateEvent);
147
+ const areUsersValid = userProfilesData.every(validateUser);
96
148
 
149
+ const invalidEvents = eventData.filter(e => !validateEvent(e));
150
+ const invalidUsers = userProfilesData.filter(u => !validateUser(u));
97
151
 
152
+ expect(areEventsValid).toBe(true);
153
+ expect(areUsersValid).toBe(true);
154
+ }, timeout);
98
155
 
99
- afterEach(() => {
156
+ test('every date is valid', async () => {
157
+ console.log('DATE TEST');
158
+ const results = await generate({ ...simple, writeToDisk: false, verbose: true });
159
+ const { eventData } = results;
160
+ const invalidDates = eventData.filter(e => !validateTime(e.time));
161
+ expect(eventData.every(e => validateTime(e.time))).toBe(true);
162
+
163
+ }, timeout);
100
164
 
101
165
  });
102
166
 
103
167
  afterAll(() => {
168
+ clearData();
169
+ });
170
+
171
+ //helpers
172
+
173
+ function clearData() {
104
174
  try {
105
175
  console.log('clearing...');
106
176
  execSync(`npm run prune`);
@@ -109,9 +179,7 @@ afterAll(() => {
109
179
  catch (err) {
110
180
  console.log('error clearing files');
111
181
  }
112
- });
113
-
114
- //helpers
182
+ }
115
183
 
116
184
  function validateEvent(event) {
117
185
  if (!event.event) return false;
@@ -128,4 +196,12 @@ function validateUser(user) {
128
196
  if (!user.$email) return false;
129
197
  if (!user.$created) return false;
130
198
  return true;
199
+ }
200
+
201
+
202
+ function validateTime(str) {
203
+ if (!str) return false;
204
+ if (str.startsWith('-')) return false;
205
+ if (!str.startsWith('20')) return false;
206
+ return true;
131
207
  }