@eventvisor/core 0.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.
Files changed (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/jest.config.js +4 -0
  4. package/lib/builder/buildProject.d.ts +21 -0
  5. package/lib/builder/buildProject.js +153 -0
  6. package/lib/builder/buildProject.js.map +1 -0
  7. package/lib/builder/hashes.d.ts +2 -0
  8. package/lib/builder/hashes.js +59 -0
  9. package/lib/builder/hashes.js.map +1 -0
  10. package/lib/builder/index.d.ts +1 -0
  11. package/lib/builder/index.js +18 -0
  12. package/lib/builder/index.js.map +1 -0
  13. package/lib/cli/cli.d.ts +26 -0
  14. package/lib/cli/cli.js +69 -0
  15. package/lib/cli/cli.js.map +1 -0
  16. package/lib/cli/index.d.ts +1 -0
  17. package/lib/cli/index.js +18 -0
  18. package/lib/cli/index.js.map +1 -0
  19. package/lib/cli/plugins.d.ts +4 -0
  20. package/lib/cli/plugins.js +12 -0
  21. package/lib/cli/plugins.js.map +1 -0
  22. package/lib/config/index.d.ts +2 -0
  23. package/lib/config/index.js +19 -0
  24. package/lib/config/index.js.map +1 -0
  25. package/lib/config/parsers.d.ts +15 -0
  26. package/lib/config/parsers.js +65 -0
  27. package/lib/config/parsers.js.map +1 -0
  28. package/lib/config/projectConfig.d.ts +42 -0
  29. package/lib/config/projectConfig.js +137 -0
  30. package/lib/config/projectConfig.js.map +1 -0
  31. package/lib/datasource/adapter.d.ts +16 -0
  32. package/lib/datasource/adapter.js +7 -0
  33. package/lib/datasource/adapter.js.map +1 -0
  34. package/lib/datasource/datasource.d.ts +48 -0
  35. package/lib/datasource/datasource.js +117 -0
  36. package/lib/datasource/datasource.js.map +1 -0
  37. package/lib/datasource/filesystemAdapter.d.ts +29 -0
  38. package/lib/datasource/filesystemAdapter.js +192 -0
  39. package/lib/datasource/filesystemAdapter.js.map +1 -0
  40. package/lib/datasource/index.d.ts +3 -0
  41. package/lib/datasource/index.js +20 -0
  42. package/lib/datasource/index.js.map +1 -0
  43. package/lib/dependencies.d.ts +11 -0
  44. package/lib/dependencies.js +3 -0
  45. package/lib/dependencies.js.map +1 -0
  46. package/lib/index.d.ts +4 -0
  47. package/lib/index.js +21 -0
  48. package/lib/index.js.map +1 -0
  49. package/lib/index.spec.d.ts +0 -0
  50. package/lib/index.spec.js +6 -0
  51. package/lib/index.spec.js.map +1 -0
  52. package/lib/init/index.d.ts +8 -0
  53. package/lib/init/index.js +90 -0
  54. package/lib/init/index.js.map +1 -0
  55. package/lib/linter/attributeSchema.d.ts +5 -0
  56. package/lib/linter/attributeSchema.js +55 -0
  57. package/lib/linter/attributeSchema.js.map +1 -0
  58. package/lib/linter/conditionsSchema.d.ts +303 -0
  59. package/lib/linter/conditionsSchema.js +106 -0
  60. package/lib/linter/conditionsSchema.js.map +1 -0
  61. package/lib/linter/destinationSchema.d.ts +5 -0
  62. package/lib/linter/destinationSchema.js +57 -0
  63. package/lib/linter/destinationSchema.js.map +1 -0
  64. package/lib/linter/effectSchema.d.ts +1257 -0
  65. package/lib/linter/effectSchema.js +77 -0
  66. package/lib/linter/effectSchema.js.map +1 -0
  67. package/lib/linter/eventSchema.d.ts +5 -0
  68. package/lib/linter/eventSchema.js +70 -0
  69. package/lib/linter/eventSchema.js.map +1 -0
  70. package/lib/linter/index.d.ts +1 -0
  71. package/lib/linter/index.js +18 -0
  72. package/lib/linter/index.js.map +1 -0
  73. package/lib/linter/jsonSchema.d.ts +25 -0
  74. package/lib/linter/jsonSchema.js +487 -0
  75. package/lib/linter/jsonSchema.js.map +1 -0
  76. package/lib/linter/jsonSchema.spec.d.ts +1 -0
  77. package/lib/linter/jsonSchema.spec.js +875 -0
  78. package/lib/linter/jsonSchema.spec.js.map +1 -0
  79. package/lib/linter/lintProject.d.ts +2 -0
  80. package/lib/linter/lintProject.js +141 -0
  81. package/lib/linter/lintProject.js.map +1 -0
  82. package/lib/linter/persistSchema.d.ts +609 -0
  83. package/lib/linter/persistSchema.js +52 -0
  84. package/lib/linter/persistSchema.js.map +1 -0
  85. package/lib/linter/printError.d.ts +9 -0
  86. package/lib/linter/printError.js +75 -0
  87. package/lib/linter/printError.js.map +1 -0
  88. package/lib/linter/sampleSchema.d.ts +331 -0
  89. package/lib/linter/sampleSchema.js +70 -0
  90. package/lib/linter/sampleSchema.js.map +1 -0
  91. package/lib/linter/sourceSchema.d.ts +11 -0
  92. package/lib/linter/sourceSchema.js +73 -0
  93. package/lib/linter/sourceSchema.js.map +1 -0
  94. package/lib/linter/tagsSchema.d.ts +3 -0
  95. package/lib/linter/tagsSchema.js +44 -0
  96. package/lib/linter/tagsSchema.js.map +1 -0
  97. package/lib/linter/testSchema.d.ts +5 -0
  98. package/lib/linter/testSchema.js +44 -0
  99. package/lib/linter/testSchema.js.map +1 -0
  100. package/lib/linter/transformsSchema.d.ts +29 -0
  101. package/lib/linter/transformsSchema.js +66 -0
  102. package/lib/linter/transformsSchema.js.map +1 -0
  103. package/lib/tester/createTestInstance.d.ts +16 -0
  104. package/lib/tester/createTestInstance.js +158 -0
  105. package/lib/tester/createTestInstance.js.map +1 -0
  106. package/lib/tester/executeTest.d.ts +24 -0
  107. package/lib/tester/executeTest.js +305 -0
  108. package/lib/tester/executeTest.js.map +1 -0
  109. package/lib/tester/index.d.ts +1 -0
  110. package/lib/tester/index.js +18 -0
  111. package/lib/tester/index.js.map +1 -0
  112. package/lib/tester/printTestResult.d.ts +10 -0
  113. package/lib/tester/printTestResult.js +80 -0
  114. package/lib/tester/printTestResult.js.map +1 -0
  115. package/lib/tester/testProject.d.ts +12 -0
  116. package/lib/tester/testProject.js +93 -0
  117. package/lib/tester/testProject.js.map +1 -0
  118. package/lib/utils/index.d.ts +1 -0
  119. package/lib/utils/index.js +18 -0
  120. package/lib/utils/index.js.map +1 -0
  121. package/lib/utils/prettyDuration.d.ts +1 -0
  122. package/lib/utils/prettyDuration.js +27 -0
  123. package/lib/utils/prettyDuration.js.map +1 -0
  124. package/package.json +42 -0
  125. package/src/builder/buildProject.ts +222 -0
  126. package/src/builder/hashes.ts +30 -0
  127. package/src/builder/index.ts +1 -0
  128. package/src/cli/cli.ts +110 -0
  129. package/src/cli/index.ts +1 -0
  130. package/src/cli/plugins.ts +13 -0
  131. package/src/config/index.ts +2 -0
  132. package/src/config/parsers.ts +40 -0
  133. package/src/config/projectConfig.ts +158 -0
  134. package/src/datasource/adapter.ts +23 -0
  135. package/src/datasource/datasource.ts +164 -0
  136. package/src/datasource/filesystemAdapter.ts +206 -0
  137. package/src/datasource/index.ts +3 -0
  138. package/src/dependencies.ts +13 -0
  139. package/src/index.spec.ts +5 -0
  140. package/src/index.ts +4 -0
  141. package/src/init/index.ts +65 -0
  142. package/src/linter/attributeSchema.ts +23 -0
  143. package/src/linter/conditionsSchema.ts +89 -0
  144. package/src/linter/destinationSchema.ts +25 -0
  145. package/src/linter/effectSchema.ts +49 -0
  146. package/src/linter/eventSchema.ts +40 -0
  147. package/src/linter/index.ts +1 -0
  148. package/src/linter/jsonSchema.spec.ts +934 -0
  149. package/src/linter/jsonSchema.ts +533 -0
  150. package/src/linter/lintProject.ts +182 -0
  151. package/src/linter/persistSchema.ts +21 -0
  152. package/src/linter/printError.ts +50 -0
  153. package/src/linter/sampleSchema.ts +45 -0
  154. package/src/linter/sourceSchema.ts +42 -0
  155. package/src/linter/tagsSchema.ts +12 -0
  156. package/src/linter/testSchema.ts +9 -0
  157. package/src/linter/transformsSchema.ts +35 -0
  158. package/src/tester/createTestInstance.ts +209 -0
  159. package/src/tester/executeTest.ts +436 -0
  160. package/src/tester/index.ts +1 -0
  161. package/src/tester/printTestResult.ts +60 -0
  162. package/src/tester/testProject.ts +129 -0
  163. package/src/utils/index.ts +1 -0
  164. package/src/utils/prettyDuration.ts +27 -0
  165. package/tsconfig.cjs.json +11 -0
@@ -0,0 +1,436 @@
1
+ import type { DatafileContent, Test, Value } from "@eventvisor/types";
2
+
3
+ import type { TestProjectOptions } from "./testProject";
4
+ import type { Dependencies } from "../dependencies";
5
+ import { createTestInstance } from "./createTestInstance";
6
+
7
+ export interface TestAssertionResult {
8
+ passed: boolean;
9
+ description?: string;
10
+ errors?: string[];
11
+ }
12
+
13
+ export interface TestAssertionResult {
14
+ passed: boolean;
15
+ description?: string;
16
+ errors?: string[];
17
+ }
18
+
19
+ export interface TestResult {
20
+ passed: boolean;
21
+ assertions: TestAssertionResult[];
22
+ }
23
+
24
+ export interface ExecuteTestOptions {
25
+ deps: Dependencies;
26
+ datafileContent: DatafileContent;
27
+ test: Test;
28
+ cliOptions: TestProjectOptions;
29
+ }
30
+
31
+ export async function executeTest(options: ExecuteTestOptions) {
32
+ const { datafileContent, test, cliOptions } = options;
33
+
34
+ const testResult: TestResult = {
35
+ passed: true,
36
+ assertions: [],
37
+ };
38
+
39
+ let testPassed = true;
40
+
41
+ /**
42
+ * Attribute
43
+ */
44
+ if ("attribute" in test) {
45
+ // attribute test spec
46
+ const attributeName = test.attribute;
47
+ const assertions = test.assertions;
48
+
49
+ for (const assertion of assertions) {
50
+ // @TODO: apply matrix
51
+
52
+ if (
53
+ cliOptions.assertionPattern &&
54
+ !assertion.description?.includes(cliOptions.assertionPattern)
55
+ ) {
56
+ continue;
57
+ }
58
+
59
+ let assertionPassed = true;
60
+
61
+ const { e } = createTestInstance({
62
+ datafile: datafileContent,
63
+ cliOptions,
64
+ withLookups: assertion.withLookups,
65
+ });
66
+
67
+ if (assertion.setAttribute) {
68
+ await e.setAttributeAsync(attributeName, assertion.setAttribute);
69
+ }
70
+
71
+ const previouslySetAttribute = e.getAttributeValue(attributeName);
72
+ const isAttributeSet = e.isAttributeSet(attributeName);
73
+
74
+ const testAssertionResult: TestAssertionResult = {
75
+ passed: assertionPassed,
76
+ description: assertion.description,
77
+ errors: [],
78
+ };
79
+
80
+ // expectedToBeValid
81
+ if (typeof assertion.expectedToBeValid === "boolean") {
82
+ if (
83
+ (assertion.expectedToBeValid === true && !isAttributeSet) ||
84
+ (assertion.expectedToBeValid === false && isAttributeSet)
85
+ ) {
86
+ assertionPassed = false;
87
+ testAssertionResult.errors?.push(
88
+ `expectedToBeValid: expected ${assertion.expectedToBeValid}, received ${!assertion.expectedToBeValid}`,
89
+ );
90
+ }
91
+ }
92
+
93
+ // expectedToBeSet
94
+ if (typeof assertion.expectedToBeSet === "boolean") {
95
+ if (
96
+ (assertion.expectedToBeSet === true && !isAttributeSet) ||
97
+ (assertion.expectedToBeSet === false && isAttributeSet)
98
+ ) {
99
+ assertionPassed = false;
100
+ testAssertionResult.errors?.push(
101
+ `expectedToBeSet: expected ${assertion.expectedToBeSet}, received ${!assertion.expectedToBeSet}`,
102
+ );
103
+ }
104
+ }
105
+
106
+ if (assertion.expectedAttribute) {
107
+ if (!checkIfObjectsAreDeepEqual(previouslySetAttribute, assertion.expectedAttribute)) {
108
+ assertionPassed = false;
109
+ testAssertionResult.errors?.push(
110
+ `expectedAttribute: \n expected: ${JSON.stringify(assertion.expectedAttribute)}\n received: ${JSON.stringify(
111
+ previouslySetAttribute,
112
+ )}`,
113
+ );
114
+ }
115
+ }
116
+
117
+ testAssertionResult.passed = assertionPassed;
118
+
119
+ if (!assertionPassed) {
120
+ testPassed = false;
121
+ }
122
+
123
+ testResult.assertions.push(testAssertionResult);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Effect
129
+ */
130
+ if ("effect" in test) {
131
+ const effectName = test.effect;
132
+ const assertions = test.assertions;
133
+
134
+ for (const assertion of assertions) {
135
+ // @TODO: apply matrix
136
+
137
+ if (
138
+ cliOptions.assertionPattern &&
139
+ !assertion.description?.includes(cliOptions.assertionPattern)
140
+ ) {
141
+ continue;
142
+ }
143
+
144
+ let assertionPassed = true;
145
+
146
+ const { e, getCalledStepsBySingleEffect } = createTestInstance({
147
+ datafile: datafileContent,
148
+ cliOptions,
149
+ withLookups: assertion.withLookups,
150
+ });
151
+
152
+ await e.onReady();
153
+
154
+ if (assertion.actions) {
155
+ for (const action of assertion.actions) {
156
+ if (action.type === "track") {
157
+ await e.trackAsync(action.name, action.value);
158
+ } else if (action.type === "setAttribute") {
159
+ await e.setAttributeAsync(action.name, action.value);
160
+ }
161
+ }
162
+ }
163
+
164
+ const testAssertionResult: TestAssertionResult = {
165
+ passed: assertionPassed,
166
+ description: assertion.description,
167
+ errors: [],
168
+ };
169
+
170
+ // expectedState
171
+ if (assertion.expectedState) {
172
+ const latestState = e.getStateValue(effectName);
173
+
174
+ if (!checkIfObjectsAreDeepEqual(latestState, assertion.expectedState)) {
175
+ assertionPassed = false;
176
+ testAssertionResult.errors?.push(
177
+ `expectedState: \n expected: ${JSON.stringify(assertion.expectedState)}\n received: ${JSON.stringify(latestState)}`,
178
+ );
179
+ }
180
+ }
181
+
182
+ // expectedHandlersToBeCalled
183
+ if (assertion.expectedToBeCalled) {
184
+ const calledSteps = getCalledStepsBySingleEffect(effectName);
185
+
186
+ for (const expectedToBeCalled of assertion.expectedToBeCalled) {
187
+ const { handler, times } = expectedToBeCalled;
188
+
189
+ const calledStepsForHandler = calledSteps?.filter((step) => step.handler === handler);
190
+
191
+ if (times) {
192
+ if (calledStepsForHandler?.length !== times) {
193
+ assertionPassed = false;
194
+ testAssertionResult.errors?.push(
195
+ `expectedToBeCalled: expected handler "${handler}" to be called ${times} times, received ${calledStepsForHandler?.length} times`,
196
+ );
197
+ }
198
+ } else {
199
+ if (!calledStepsForHandler?.length || calledStepsForHandler.length === 0) {
200
+ assertionPassed = false;
201
+ testAssertionResult.errors?.push(
202
+ `expectedToBeCalled: expected handler "${handler}" to be called at least once`,
203
+ );
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ testAssertionResult.passed = assertionPassed;
210
+
211
+ if (!assertionPassed) {
212
+ testPassed = false;
213
+ }
214
+
215
+ testResult.assertions.push(testAssertionResult);
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Event
221
+ */
222
+ if ("event" in test) {
223
+ const eventName = test.event;
224
+ const assertions = test.assertions;
225
+
226
+ for (const assertion of assertions) {
227
+ // @TODO: apply matrix
228
+
229
+ if (
230
+ cliOptions.assertionPattern &&
231
+ !assertion.description?.includes(cliOptions.assertionPattern)
232
+ ) {
233
+ continue;
234
+ }
235
+
236
+ let assertionPassed = true;
237
+
238
+ const { e } = createTestInstance({
239
+ datafile: datafileContent,
240
+ cliOptions,
241
+ withLookups: assertion.withLookups,
242
+ });
243
+
244
+ let trackedEvent: Value | null = null;
245
+
246
+ if (assertion.track) {
247
+ trackedEvent = await e.trackAsync(eventName, assertion.track);
248
+ }
249
+
250
+ if (assertion.actions) {
251
+ for (const action of assertion.actions) {
252
+ if (action.type === "track") {
253
+ await e.trackAsync(action.name, action.value);
254
+ } else if (action.type === "setAttribute") {
255
+ await e.setAttributeAsync(action.name, action.value);
256
+ }
257
+ }
258
+ }
259
+
260
+ const testAssertionResult: TestAssertionResult = {
261
+ passed: assertionPassed,
262
+ description: assertion.description,
263
+ errors: [],
264
+ };
265
+
266
+ // expectedToBeValid
267
+ if (typeof assertion.expectedToBeValid === "boolean") {
268
+ if (
269
+ (assertion.expectedToBeValid === true && !trackedEvent) ||
270
+ (assertion.expectedToBeValid === false && trackedEvent)
271
+ ) {
272
+ assertionPassed = false;
273
+ testAssertionResult.errors?.push(
274
+ `expectedToBeValid: expected ${assertion.expectedToBeValid}, received ${!assertion.expectedToBeValid}`,
275
+ );
276
+ }
277
+ }
278
+
279
+ // expectedToContinue
280
+ if (assertion.expectedToContinue) {
281
+ // @TODO: implement
282
+ }
283
+
284
+ // expectedEvent
285
+ if (assertion.expectedEvent) {
286
+ if (!checkIfObjectsAreDeepEqual(trackedEvent, assertion.expectedEvent)) {
287
+ assertionPassed = false;
288
+ testAssertionResult.errors?.push(
289
+ `expectedEvent: \n expected: ${JSON.stringify(assertion.expectedEvent)}\n received: ${JSON.stringify(trackedEvent)}`,
290
+ );
291
+ }
292
+ }
293
+
294
+ // expectedDestinations
295
+ if (assertion.expectedDestinations) {
296
+ // if (!assertion.expectedDestinations.includes(m.getDestinationName())) {
297
+ // assertionPassed = false;
298
+ // testAssertionResult.errors?.push(
299
+ // `expectedDestinations: expected ${assertion.expectedDestinations.join(", ")}, received ${m.getDestinationName()}`,
300
+ // );
301
+ // }
302
+ }
303
+
304
+ // expectedDestinationsByTag
305
+ if (assertion.expectedDestinationsByTag) {
306
+ // @TODO: implement
307
+ }
308
+
309
+ testAssertionResult.passed = assertionPassed;
310
+
311
+ if (!assertionPassed) {
312
+ testPassed = false;
313
+ }
314
+
315
+ testResult.assertions.push(testAssertionResult);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Destination
321
+ */
322
+ if ("destination" in test) {
323
+ const destinationName = test.destination;
324
+ const assertions = test.assertions;
325
+
326
+ for (const assertion of assertions) {
327
+ // @TODO: apply matrix
328
+
329
+ if (
330
+ cliOptions.assertionPattern &&
331
+ !assertion.description?.includes(cliOptions.assertionPattern)
332
+ ) {
333
+ continue;
334
+ }
335
+
336
+ let assertionPassed = true;
337
+
338
+ const { e, getBodiesBySingleDestination } = createTestInstance({
339
+ datafile: datafileContent,
340
+ cliOptions,
341
+ withLookups: assertion.withLookups,
342
+ });
343
+
344
+ if (assertion.actions) {
345
+ for (const action of assertion.actions) {
346
+ if (action.type === "track") {
347
+ await e.trackAsync(action.name, action.value);
348
+ } else if (action.type === "setAttribute") {
349
+ await e.setAttributeAsync(action.name, action.value);
350
+ }
351
+ }
352
+ }
353
+
354
+ const testAssertionResult: TestAssertionResult = {
355
+ passed: assertionPassed,
356
+ description: assertion.description,
357
+ errors: [],
358
+ };
359
+
360
+ // expectedToBeTransported
361
+ if (typeof assertion.expectedToBeTransported === "boolean") {
362
+ const bodies = getBodiesBySingleDestination(destinationName);
363
+
364
+ if (
365
+ (assertion.expectedToBeTransported === false && bodies && bodies.length > 0) ||
366
+ (assertion.expectedToBeTransported === true && (!bodies || bodies.length === 0))
367
+ ) {
368
+ assertionPassed = false;
369
+ testAssertionResult.errors?.push(
370
+ `expectedToBeTransported: expected ${assertion.expectedToBeTransported}, received ${!assertion.expectedToBeTransported}`,
371
+ );
372
+ }
373
+ }
374
+
375
+ // expectedBody
376
+ if (assertion.expectedBody) {
377
+ const bodies = getBodiesBySingleDestination(destinationName);
378
+ if (!checkIfObjectsAreDeepEqual(bodies && bodies[0], assertion.expectedBody)) {
379
+ assertionPassed = false;
380
+ testAssertionResult.errors?.push(
381
+ `expectedBody: \n expected: ${JSON.stringify(assertion.expectedBody, null, 2)}\n received: ${JSON.stringify(bodies && bodies[0], null, 2)}`,
382
+ );
383
+ }
384
+ }
385
+
386
+ // @TODO: expectedBodies
387
+
388
+ testAssertionResult.passed = assertionPassed;
389
+
390
+ if (!assertionPassed) {
391
+ testPassed = false;
392
+ }
393
+
394
+ testResult.assertions.push(testAssertionResult);
395
+ }
396
+ }
397
+
398
+ testResult.passed = testPassed;
399
+
400
+ return testResult;
401
+ }
402
+
403
+ function checkIfObjectsAreDeepEqual(a: any, b: any) {
404
+ // Handle null/undefined cases
405
+ if (a === null || b === null || a === undefined || b === undefined) {
406
+ return a === b;
407
+ }
408
+
409
+ // Handle primitive types
410
+ if (typeof a !== "object" || typeof b !== "object") {
411
+ return a === b;
412
+ }
413
+
414
+ // Handle arrays
415
+ if (Array.isArray(a) && Array.isArray(b)) {
416
+ if (a.length !== b.length) {
417
+ return false;
418
+ }
419
+ return a.every((item, index) => checkIfObjectsAreDeepEqual(item, b[index]));
420
+ }
421
+
422
+ // Handle objects
423
+ const aKeys = Object.keys(a);
424
+ const bKeys = Object.keys(b);
425
+
426
+ if (aKeys.length !== bKeys.length) {
427
+ return false;
428
+ }
429
+
430
+ return aKeys.every((key) => {
431
+ if (!Object.prototype.hasOwnProperty.call(b, key)) {
432
+ return false;
433
+ }
434
+ return checkIfObjectsAreDeepEqual(a[key], b[key]);
435
+ });
436
+ }
@@ -0,0 +1 @@
1
+ export * from "./testProject";
@@ -0,0 +1,60 @@
1
+ import * as path from "path";
2
+ import chalk from "chalk";
3
+
4
+ import { Test } from "@eventvisor/types";
5
+
6
+ import { Dependencies } from "../dependencies";
7
+ import { TestResult } from "./executeTest";
8
+
9
+ function prefixLines(str: string, prefix: string) {
10
+ return str.replace(/^/gm, prefix);
11
+ }
12
+
13
+ export interface PrintTestResultOptions {
14
+ testName: string;
15
+ test: Test;
16
+ testResult: TestResult;
17
+ deps: Dependencies;
18
+ }
19
+
20
+ export function printTestResult(options: PrintTestResultOptions) {
21
+ const { testName, test, testResult, deps } = options;
22
+ const { projectConfig, rootDirectoryPath, datasource } = deps;
23
+
24
+ const relativeTestPath = path.relative(
25
+ rootDirectoryPath,
26
+ path.join(projectConfig.testsDirectoryPath, testName + "." + datasource.getExtension()),
27
+ );
28
+
29
+ console.log(`\n\n`);
30
+
31
+ if (testResult.passed) {
32
+ console.log(chalk.green(`Testing: ${relativeTestPath}`));
33
+ } else {
34
+ console.error(chalk.red(`Testing: ${relativeTestPath}`));
35
+ }
36
+
37
+ if ("attribute" in test) {
38
+ if (testResult.passed) {
39
+ console.log(chalk.green(` Attribute "${chalk.green(test.attribute)}":`));
40
+ } else {
41
+ console.error(chalk.red(` Attribute "${chalk.red(test.attribute)}":`));
42
+ }
43
+ }
44
+
45
+ if (testResult.assertions.length > 0) {
46
+ for (const assertion of testResult.assertions) {
47
+ if (assertion.passed) {
48
+ console.log(chalk.green(` ✓ ${assertion.description}`));
49
+ } else {
50
+ console.error(chalk.red(` ✗ ${assertion.description}`));
51
+ }
52
+
53
+ if (assertion.errors) {
54
+ for (const error of assertion.errors) {
55
+ console.error(chalk.red(prefixLines(error, " ")));
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,129 @@
1
+ import chalk from "chalk";
2
+
3
+ import { Dependencies } from "../dependencies";
4
+ import { Plugin } from "../cli";
5
+ import { buildDatafile } from "../builder";
6
+ import { prettyDuration } from "../utils";
7
+ import { printTestResult } from "./printTestResult";
8
+ import { executeTest } from "./executeTest";
9
+
10
+ export interface TestProjectOptions {
11
+ keyPattern?: string;
12
+ entityType?: string;
13
+ assertionPattern?: string;
14
+ onlyFailures?: boolean;
15
+ quiet?: boolean;
16
+ verbose?: boolean;
17
+ }
18
+
19
+ export async function testProject(
20
+ deps: Dependencies,
21
+ options: TestProjectOptions,
22
+ ): Promise<boolean> {
23
+ const beforeDatafileBuild = new Date();
24
+ console.log("Building datafile...");
25
+
26
+ const datafileContent = await buildDatafile(deps);
27
+ const afterDatafileBuild = new Date();
28
+ console.log(
29
+ `Datafile built in ${afterDatafileBuild.getTime() - beforeDatafileBuild.getTime()}ms`,
30
+ );
31
+
32
+ const tests = await deps.datasource.listTests();
33
+
34
+ const totals = {
35
+ specsPassed: 0,
36
+ specsFailed: 0,
37
+ assertionsPassed: 0,
38
+ assertionsFailed: 0,
39
+ };
40
+
41
+ let hasFailures = false;
42
+
43
+ const start = new Date();
44
+
45
+ for (const test of tests) {
46
+ // @TODO: use regex?
47
+ if (options.keyPattern && !test.includes(options.keyPattern)) {
48
+ continue;
49
+ }
50
+
51
+ const testContent = await deps.datasource.readTest(test);
52
+ const testResult = await executeTest({
53
+ deps,
54
+ datafileContent,
55
+ test: testContent,
56
+ cliOptions: options,
57
+ });
58
+
59
+ if (!testResult.passed) {
60
+ hasFailures = true;
61
+ totals.specsFailed++;
62
+ } else {
63
+ totals.specsPassed++;
64
+ }
65
+
66
+ for (const assertion of testResult.assertions) {
67
+ if (assertion.passed) {
68
+ totals.assertionsPassed++;
69
+ } else {
70
+ totals.assertionsFailed++;
71
+ }
72
+ }
73
+
74
+ if (typeof options.onlyFailures === "undefined" || !testResult.passed) {
75
+ // print
76
+ printTestResult({
77
+ testName: test,
78
+ test: testContent,
79
+ testResult,
80
+ deps,
81
+ });
82
+ }
83
+ }
84
+
85
+ console.log(`\n\n`);
86
+
87
+ if (hasFailures) {
88
+ console.log(
89
+ chalk.red(`Test specs: ${totals.specsPassed} passed, ${totals.specsFailed} failed`),
90
+ );
91
+ console.log(
92
+ chalk.red(`Assertions: ${totals.assertionsPassed} passed, ${totals.assertionsFailed} failed`),
93
+ );
94
+ } else {
95
+ console.log(
96
+ chalk.green(`Test specs: ${totals.specsPassed} passed, ${totals.specsFailed} failed`),
97
+ );
98
+ console.log(
99
+ chalk.green(
100
+ `Assertions: ${totals.assertionsPassed} passed, ${totals.assertionsFailed} failed`,
101
+ ),
102
+ );
103
+ }
104
+
105
+ const end = new Date();
106
+ console.log(`Time: ${prettyDuration(end.getTime() - start.getTime())}`);
107
+
108
+ if (hasFailures) {
109
+ return false;
110
+ }
111
+
112
+ return true;
113
+ }
114
+
115
+ export const testPlugin: Plugin = {
116
+ command: "test",
117
+ handler: async function ({ rootDirectoryPath, projectConfig, datasource, parsed }) {
118
+ return testProject(
119
+ {
120
+ rootDirectoryPath,
121
+ projectConfig,
122
+ datasource,
123
+ options: parsed,
124
+ },
125
+ parsed as TestProjectOptions,
126
+ );
127
+ },
128
+ examples: [],
129
+ };
@@ -0,0 +1 @@
1
+ export * from "./prettyDuration";
@@ -0,0 +1,27 @@
1
+ export function prettyDuration(duration: number) {
2
+ if (duration < 1000) {
3
+ return `${Math.floor(duration)}ms`;
4
+ }
5
+
6
+ const hours = Math.floor(duration / (1000 * 60 * 60));
7
+ const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60));
8
+ const seconds = Math.floor((duration % (1000 * 60)) / 1000);
9
+ const milliseconds = Math.floor(duration % 1000);
10
+
11
+ let result = "";
12
+
13
+ if (hours > 0) {
14
+ result += `${hours}h`;
15
+ }
16
+ if (minutes > 0) {
17
+ result += `${minutes}m`;
18
+ }
19
+ if (seconds > 0) {
20
+ result += `${seconds}s`;
21
+ }
22
+ if (milliseconds > 0) {
23
+ result += `${milliseconds}ms`;
24
+ }
25
+
26
+ return result;
27
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.cjs.json",
3
+ "compilerOptions": {
4
+ "target": "es2020",
5
+ "outDir": "./lib",
6
+ "moduleResolution": "node",
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true
9
+ },
10
+ "include": ["./src/**/*.ts"]
11
+ }