make-mp-data 1.1.19 → 1.2.1

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/tests/e2e.test.js CHANGED
@@ -8,66 +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');
11
+ const simple = require('../models/simple.js');
12
+ const complex = require('../models/complex.js');
13
+ const deep = require('../models/deepNest.js');
12
14
 
13
15
  const timeout = 60000;
14
16
  const testToken = process.env.TEST_TOKEN;
15
17
 
18
+ describe('module', () => {
16
19
 
17
- describe('e2e', () => {
18
-
19
- test('works as module', async () => {
20
+ test('works as module (no config)', async () => {
20
21
  console.log('MODULE TEST');
21
- const results = await generate({ verbose: false, 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" });
22
23
  const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
23
24
  expect(eventData.length).toBeGreaterThan(980);
24
25
  expect(groupProfilesData.length).toBe(0);
25
26
  expect(lookupTableData.length).toBe(0);
26
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);
27
40
  expect(userProfilesData.length).toBe(100);
28
41
 
29
42
  }, timeout);
30
43
 
31
- test('works as CLI', async () => {
32
- console.log('CLI TEST');
33
- const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it" --verbose false`);
34
- expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
35
- const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
36
- 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
+
37
55
  }, timeout);
38
56
 
39
- test('sends data to mixpanel', async () => {
40
- console.log('NETWORK TEST');
41
- const results = await generate({verbose: false, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it", token: testToken });
42
- const { events, users, groups } = results.import;
43
- expect(events.success).toBeGreaterThan(980);
44
- expect(users.success).toBe(100);
45
- 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
+
46
67
  }, timeout);
47
68
 
48
- test('every record is valid', async () => {
49
- console.log('VALIDATION TEST');
50
- const results = await generate({verbose: false, writeToDisk: false, numEvents: 10000, numUsers: 500 });
51
- const { eventData, userProfilesData } = results;
52
- const areEventsValid = eventData.every(validateEvent);
53
- const areUsersValid = userProfilesData.every(validateUser);
54
69
 
55
- const invalidEvents = eventData.filter(e => !validateEvent(e));
56
- const invalidUsers = userProfilesData.filter(u => !validateUser(u));
70
+ });
57
71
 
58
- expect(areEventsValid).toBe(true);
59
- 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();
60
80
  }, timeout);
61
81
 
62
- test('every date is valid', async () => {
63
- console.log('DATE TEST');
64
- const results = await generate({ ...simple, writeToDisk: false, verbose: false });
65
- const { eventData } = results;
66
- const invalidDates = eventData.filter(e => !validateTime(e.time));
67
- expect(eventData.every(e => validateTime(e.time))).toBe(true);
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);
68
90
 
69
-
70
- }, timeout)
91
+ test('works as CLI (custom)', async () => {
92
+ console.log('custom CLI TEST');
93
+ const run = execSync(`node ./index.js ./models/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);
71
99
 
72
100
  });
73
101
 
@@ -102,16 +130,47 @@ describe('options + tweaks', () => {
102
130
  expect(anonIds.length).toBe(0);
103
131
  }, timeout);
104
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);
105
141
 
106
- });
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);
107
148
 
149
+ const invalidEvents = eventData.filter(e => !validateEvent(e));
150
+ const invalidUsers = userProfilesData.filter(u => !validateUser(u));
108
151
 
152
+ expect(areEventsValid).toBe(true);
153
+ expect(areUsersValid).toBe(true);
154
+ }, timeout);
109
155
 
110
- 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);
111
164
 
112
165
  });
113
166
 
114
167
  afterAll(() => {
168
+ clearData();
169
+ });
170
+
171
+ //helpers
172
+
173
+ function clearData() {
115
174
  try {
116
175
  console.log('clearing...');
117
176
  execSync(`npm run prune`);
@@ -120,9 +179,7 @@ afterAll(() => {
120
179
  catch (err) {
121
180
  console.log('error clearing files');
122
181
  }
123
- });
124
-
125
- //helpers
182
+ }
126
183
 
127
184
  function validateEvent(event) {
128
185
  if (!event.event) return false;
@@ -7,6 +7,7 @@ const { timeSoup } = generate;
7
7
  require('dotenv').config();
8
8
 
9
9
 
10
+
10
11
  describe('timeSoup', () => {
11
12
  test('always positive dates', () => {
12
13
  const dates = [];
@@ -21,3 +22,157 @@ describe('timeSoup', () => {
21
22
 
22
23
  });
23
24
  });
25
+
26
+
27
+
28
+ const { applySkew, boxMullerRandom, choose, date, dates, day, exhaust, generateEmoji, getUniqueKeys, integer, makeHashTags, makeProducts, mapToRange, person, pick, range, weighList, weightedRange } = require('../utils');
29
+
30
+ describe('utils', () => {
31
+
32
+ test('pick: works', () => {
33
+ const array = [1, 2, 3];
34
+ const item = pick(array);
35
+ expect(array).toContain(item);
36
+ });
37
+
38
+ test('pick: null', () => {
39
+ expect(pick(123)).toBe(123);
40
+ });
41
+
42
+
43
+
44
+ test('integer: diff', () => {
45
+ const min = 5;
46
+ const max = 10;
47
+ const result = integer(min, max);
48
+ expect(result).toBeGreaterThanOrEqual(min);
49
+ expect(result).toBeLessThanOrEqual(max);
50
+ });
51
+
52
+ test('integer: same', () => {
53
+ expect(integer(7, 7)).toBe(7);
54
+ });
55
+
56
+
57
+
58
+ test('hashtags', () => {
59
+ const hashtags = makeHashTags();
60
+ expect(hashtags).toBeInstanceOf(Array);
61
+ expect(hashtags).not.toHaveLength(0);
62
+ hashtags.forEach(tag => {
63
+ expect(tag).toMatch(/^#/);
64
+ });
65
+ });
66
+
67
+
68
+
69
+ test('person: fields', () => {
70
+ const generatedPerson = person();
71
+ expect(generatedPerson).toHaveProperty('$name');
72
+ expect(generatedPerson).toHaveProperty('$email');
73
+ expect(generatedPerson).toHaveProperty('$avatar');
74
+ expect(generatedPerson).toHaveProperty('anonymousIds');
75
+ expect(generatedPerson.anonymousIds).toBeInstanceOf(Array);
76
+ });
77
+
78
+
79
+ test('date: past date', () => {
80
+ const pastDate = date(10, true, 'YYYY-MM-DD')();
81
+ expect(dayjs(pastDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
82
+ expect(dayjs(pastDate).isBefore(dayjs())).toBeTruthy();
83
+ });
84
+
85
+ test('date: future date', () => {
86
+ const futureDate = date(10, false, 'YYYY-MM-DD')();
87
+ expect(dayjs(futureDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
88
+ expect(dayjs(futureDate).isAfter(dayjs())).toBeTruthy();
89
+ });
90
+
91
+ test('dates: returns pairs of dates', () => {
92
+ const datePairs = dates(10, 3, 'YYYY-MM-DD');
93
+ expect(datePairs).toBeInstanceOf(Array);
94
+ expect(datePairs).toHaveLength(3);
95
+ datePairs.forEach(pair => {
96
+ expect(pair).toHaveLength(2);
97
+ });
98
+ });
99
+
100
+ test('choose: choose from array', () => {
101
+ const options = ['apple', 'banana', 'cherry'];
102
+ const choice = choose(options);
103
+ expect(options).toContain(choice);
104
+ });
105
+
106
+ test('choose: execute function', () => {
107
+ const result = choose(() => 'test');
108
+ expect(result).toBe('test');
109
+ });
110
+
111
+ test('exhaust: exhaust array elements', () => {
112
+ const arr = [1, 2, 3];
113
+ const exhaustFn = exhaust([...arr]);
114
+ expect(exhaustFn()).toBe(1);
115
+ expect(exhaustFn()).toBe(2);
116
+ expect(exhaustFn()).toBe(3);
117
+ expect(exhaustFn()).toBeUndefined();
118
+ });
119
+
120
+ test('generateEmoji: returns string of emojis', () => {
121
+ const emojis = generateEmoji(5)();
122
+ expect(typeof emojis).toBe('string');
123
+ expect(emojis.split(', ').length).toBeLessThanOrEqual(5);
124
+ });
125
+
126
+ test('getUniqueKeys: find unique keys', () => {
127
+ const objects = [{ a: 1, b: 2 }, { a: 3, c: 4 }, { a: 5, b: 6 }];
128
+ const uniqueKeys = getUniqueKeys(objects);
129
+ expect(uniqueKeys).toEqual(expect.arrayContaining(['a', 'b', 'c']));
130
+ });
131
+
132
+
133
+ test('date: generates a valid date', () => {
134
+ const result = date();
135
+ expect(dayjs(result()).isValid()).toBe(true);
136
+ });
137
+
138
+ test('dates: generates an array of date pairs', () => {
139
+ const result = dates();
140
+ expect(result).toBeInstanceOf(Array);
141
+ expect(result.length).toBe(5); // Assuming default numPairs is 5
142
+ result.forEach(pair => {
143
+ expect(pair).toBeInstanceOf(Array);
144
+ expect(pair.length).toBe(2);
145
+ expect(dayjs(pair[0]()).isValid()).toBe(true);
146
+ expect(dayjs(pair[1]()).isValid()).toBe(true);
147
+ });
148
+ });
149
+
150
+ test('day: generates a day within range', () => {
151
+ const start = '2020-01-01';
152
+ const end = '2020-01-30';
153
+ const result = day(start, end);
154
+ const dayResult = result(0, 9);
155
+ expect(dayjs(dayResult.day).isAfter(dayjs(dayResult.start))).toBe(true);
156
+ expect(dayjs(dayResult.day).isBefore(dayjs(dayResult.end))).toBe(true);
157
+ });
158
+
159
+ test('exhaust: sequentially removes items from array', () => {
160
+ const arr = [1, 2, 3];
161
+ const next = exhaust(arr);
162
+ expect(next()).toBe(1);
163
+ expect(next()).toBe(2);
164
+ expect(next()).toBe(3);
165
+ expect(next()).toBe(undefined); // or whatever your implementation does after array is exhausted
166
+ });
167
+
168
+ test('generateEmoji: generates correct format and length', () => {
169
+ const result = generateEmoji();
170
+ const emojis = result();
171
+ expect(typeof emojis).toBe('string');
172
+ const emojiArray = emojis.split(', ');
173
+ expect(emojiArray.length).toBeLessThanOrEqual(10); // Assuming max default is 10
174
+
175
+ });
176
+
177
+
178
+ });
package/timesoup.js ADDED
@@ -0,0 +1,92 @@
1
+ const Chance = require("chance");
2
+ const chance = new Chance();
3
+ const dayjs = require("dayjs");
4
+ const utc = require("dayjs/plugin/utc");
5
+ dayjs.extend(utc);
6
+ const { integer } = require('./utils');
7
+ const NOW = dayjs().unix();
8
+
9
+ const PEAK_DAYS = [
10
+ dayjs().subtract(2, "day").unix(),
11
+ dayjs().subtract(3, "day").unix(),
12
+ dayjs().subtract(5, "day").unix(),
13
+ dayjs().subtract(7, "day").unix(),
14
+ dayjs().subtract(11, "day").unix(),
15
+ dayjs().subtract(13, "day").unix(),
16
+ dayjs().subtract(17, "day").unix(),
17
+ dayjs().subtract(19, "day").unix(),
18
+ dayjs().subtract(23, "day").unix(),
19
+ dayjs().subtract(29, "day").unix(),
20
+ ];
21
+
22
+
23
+ /**
24
+ * essentially, a timestamp generator with a twist
25
+ * @param {number} earliestTime - The earliest timestamp in Unix format.
26
+ * @param {number} latestTime - The latest timestamp in Unix format.
27
+ * @param {Array} peakDays - Array of Unix timestamps representing the start of peak days.
28
+ * @returns {number} - The generated event timestamp in Unix format.
29
+ */
30
+ function AKsTimeSoup(earliestTime, latestTime = NOW, peakDays = PEAK_DAYS) {
31
+ let chosenTime;
32
+ let eventTime;
33
+ let validTime = false;
34
+
35
+ if (typeof earliestTime !== "number") {
36
+ if (parseInt(earliestTime) > 0) earliestTime = parseInt(earliestTime);
37
+ if (dayjs(earliestTime).isValid()) earliestTime = dayjs(earliestTime).unix();
38
+ }
39
+
40
+ while (!validTime) {
41
+
42
+ // Define business hours
43
+ const peakStartHour = 4; // 4 AM
44
+ const peakEndHour = 23; // 11 PM
45
+ const likelihoodOfPeakDay = chance.integer({ min: integer(5, 42), max: integer(43, 69) }); // Randomize likelihood with CHAOS!~~
46
+
47
+ // Select a day, with a preference for peak days
48
+ let selectedDay;
49
+ if (chance.bool({ likelihood: likelihoodOfPeakDay })) { // Randomized likelihood to pick a peak day
50
+ selectedDay = peakDays.length > 0 ? chance.pickone(peakDays) : integer(earliestTime, latestTime);
51
+ } else {
52
+ // Introduce minor peaks by allowing some events to still occur during business hours
53
+ selectedDay = chance.bool({ likelihood: integer(1, 42) })
54
+ ? chance.pickone(peakDays)
55
+ : integer(earliestTime, latestTime);
56
+ }
57
+
58
+ // Normalize selectedDay to the start of the day
59
+ selectedDay = dayjs.unix(selectedDay).startOf('day').unix();
60
+
61
+ // Generate a random time within business hours with a higher concentration in the middle of the period
62
+ const businessStart = dayjs.unix(selectedDay).hour(peakStartHour).minute(0).second(0).unix();
63
+ const businessEnd = dayjs.unix(selectedDay).hour(peakEndHour).minute(0).second(0).unix();
64
+
65
+ if (selectedDay === peakDays[0]) {
66
+ // Use a skewed distribution for peak days
67
+ eventTime = chance.normal({ mean: (businessEnd + businessStart) / integer(1, 4), dev: (businessEnd - businessStart) / integer(2, 8) });
68
+ } else {
69
+ // For non-peak days, use a uniform distribution to add noise
70
+ eventTime = integer(integer(businessStart, businessEnd), integer(businessStart, businessEnd));
71
+ }
72
+
73
+ // usually, ensure the event time is within business hours
74
+ if (chance.bool({ likelihood: 42 })) eventTime = Math.min(Math.max(eventTime, businessStart), businessEnd);
75
+
76
+ if (eventTime > 0) validTime = true;
77
+ const parsedTime = dayjs.unix(eventTime).toISOString();
78
+ if (!parsedTime.startsWith('20')) validTime = false;
79
+
80
+ }
81
+ chosenTime = dayjs.unix(eventTime).toISOString();
82
+
83
+ //should never get here
84
+ if (eventTime < 0) debugger;
85
+ if (!chosenTime.startsWith('20')) debugger;
86
+
87
+
88
+ return chosenTime;
89
+ }
90
+
91
+
92
+ module.exports = AKsTimeSoup;