@zohodesk/testinglibrary 0.1.9-exp-actors → 0.1.12-experimental

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 (241) hide show
  1. package/.babelrc +4 -2
  2. package/.claude/settings.local.json +14 -0
  3. package/.gitlab-ci.yml +206 -0
  4. package/README.md +264 -1
  5. package/build/common/data-generator/steps/DataGenerator.spec.js +19 -0
  6. package/build/common/data-generator/steps/DataGeneratorStepsHelper.js +21 -0
  7. package/build/common/multi-actor/steps/multiActorHandling.spec.js +26 -0
  8. package/build/common/searchFake/helpers/rpcRequestHelper.js +52 -0
  9. package/build/common/searchFake/steps/searchFake.spec.js +85 -0
  10. package/build/core/dataGenerator/DataGenerator.js +161 -0
  11. package/build/core/dataGenerator/DataGeneratorError.js +60 -0
  12. package/build/core/dataGenerator/DataGeneratorHelper.js +121 -0
  13. package/build/core/dataGenerator/authenticator/Authenticator.js +37 -0
  14. package/build/core/dataGenerator/authenticator/CookieAuthenticator.js +48 -0
  15. package/build/core/dataGenerator/authenticator/JWTauthenticator.js +59 -0
  16. package/build/core/dataGenerator/authenticator/OAuthAuthenticator.js +26 -0
  17. package/build/core/dataGenerator/authenticator/OrgOAuthAuthenticator.js +26 -0
  18. package/build/core/dataGenerator/authenticator/cookieAuthConfig.js +11 -0
  19. package/build/core/jest/preprocessor/jsPreprocessor.js +2 -7
  20. package/build/core/jest/setup/index.js +1 -7
  21. package/build/core/playwright/builtInFixtures/actorContext.js +78 -0
  22. package/build/core/playwright/builtInFixtures/caseTimeout.js +20 -0
  23. package/build/core/playwright/builtInFixtures/context.js +34 -16
  24. package/build/core/playwright/builtInFixtures/executionContext.js +17 -0
  25. package/build/core/playwright/builtInFixtures/i18N.js +8 -0
  26. package/build/core/playwright/builtInFixtures/index.js +7 -1
  27. package/build/core/playwright/builtInFixtures/page.js +22 -84
  28. package/build/core/playwright/clear-caches.js +17 -5
  29. package/build/core/playwright/codegen.js +4 -21
  30. package/build/core/playwright/configuration/Configuration.js +25 -0
  31. package/build/core/playwright/configuration/ConfigurationHelper.js +45 -0
  32. package/build/core/playwright/configuration/UserArgs.js +12 -0
  33. package/build/core/playwright/constants/configConstants.js +17 -0
  34. package/build/core/playwright/constants/failureTagConstants.js +17 -0
  35. package/build/core/playwright/constants/fileMutexConfig.js +9 -0
  36. package/build/core/playwright/constants/reporterConstants.js +20 -0
  37. package/build/core/playwright/constants/retryConstants.js +8 -0
  38. package/build/core/playwright/custom-commands.js +1 -1
  39. package/build/core/playwright/env-initializer.js +26 -7
  40. package/build/core/playwright/fixtures.js +24 -0
  41. package/build/core/playwright/helpers/additionalProfiles.js +25 -0
  42. package/build/core/playwright/helpers/auth/accountLogin.js +4 -1
  43. package/build/core/playwright/helpers/auth/checkAuthCookies.js +20 -19
  44. package/build/core/playwright/helpers/auth/getUsers.js +57 -49
  45. package/build/core/playwright/helpers/auth/index.js +14 -1
  46. package/build/core/playwright/helpers/auth/loginDefaultStepsHelper.js +62 -0
  47. package/build/core/playwright/helpers/auth/loginSteps.js +40 -22
  48. package/build/core/playwright/helpers/browserTimeoutFactor.js +16 -0
  49. package/build/core/playwright/helpers/caseTimeoutResolver.js +37 -0
  50. package/build/core/playwright/helpers/checkAuthDirectory.js +27 -0
  51. package/build/core/playwright/helpers/configFileNameProvider.js +21 -7
  52. package/build/core/playwright/helpers/configPathResolver.js +38 -0
  53. package/build/core/playwright/helpers/contextFailureHandler.js +81 -0
  54. package/build/core/playwright/helpers/customFixturesHelper.js +58 -0
  55. package/build/core/playwright/helpers/failureTagClassifier.js +197 -0
  56. package/build/core/playwright/helpers/fileMutex.js +71 -0
  57. package/build/core/playwright/helpers/logCollector.js +147 -0
  58. package/build/core/playwright/helpers/parseUserArgs.js +2 -3
  59. package/build/core/playwright/helpers/runtimeLogAnnotator.js +140 -0
  60. package/build/core/playwright/helpers/runtimeLogPath.js +61 -0
  61. package/build/core/playwright/helpers/runtimeLogReader.js +54 -0
  62. package/build/core/playwright/helpers/thirdPartyLib.js +13 -0
  63. package/build/core/playwright/index.js +8 -88
  64. package/build/core/playwright/patterns/casePatterns.js +9 -0
  65. package/build/core/playwright/patterns/codePatterns.js +9 -0
  66. package/build/core/playwright/patterns/environmentPatterns.js +15 -0
  67. package/build/core/playwright/patterns/index.js +33 -0
  68. package/build/core/playwright/patterns/logCollectorIgnorePatterns.js +15 -0
  69. package/build/core/playwright/patterns/toolPatterns.js +26 -0
  70. package/build/core/playwright/readConfigFile.js +49 -16
  71. package/build/core/playwright/reporter/PlaywrightReporter.js +58 -0
  72. package/build/core/playwright/reporter/Reporter.js +65 -0
  73. package/build/core/playwright/reporter/ShardReporter.js +58 -0
  74. package/build/core/playwright/reporter/UnitReporter.js +27 -0
  75. package/build/core/playwright/reporter/helpers/mergeAbortedTests.js +78 -0
  76. package/build/core/playwright/runner/Runner.js +22 -0
  77. package/build/core/playwright/runner/RunnerHelper.js +43 -0
  78. package/build/core/playwright/runner/RunnerTypes.js +17 -0
  79. package/build/core/playwright/runner/SpawnRunner.js +93 -0
  80. package/build/core/playwright/setup/Project.js +35 -0
  81. package/build/core/playwright/setup/ProjectConfiguration.js +66 -0
  82. package/build/core/playwright/setup/config-creator.js +58 -40
  83. package/build/core/playwright/setup/config-utils.js +68 -37
  84. package/build/core/playwright/setup/custom-reporter.js +137 -14
  85. package/build/core/playwright/setup/merge-config.js +32 -0
  86. package/build/core/playwright/setup/qc-custom-reporter.js +342 -0
  87. package/build/core/playwright/spawnInheritChild.js +71 -0
  88. package/build/core/playwright/tagProcessor.js +69 -0
  89. package/build/core/playwright/test-runner.js +44 -97
  90. package/build/core/playwright/types.js +4 -1
  91. package/build/core/playwright/validateFeature.js +28 -0
  92. package/build/decorators.d.ts +1 -1
  93. package/build/decorators.js +1 -1
  94. package/build/index.d.ts +2 -22
  95. package/build/index.js +12 -2
  96. package/build/lib/cli.js +42 -7
  97. package/build/setup-folder-structure/helper.js +2 -0
  98. package/build/setup-folder-structure/samples/accountLogin-sample.js +19 -0
  99. package/build/setup-folder-structure/samples/actors-index.js +2 -0
  100. package/build/setup-folder-structure/samples/auth-setup-sample.js +10 -66
  101. package/build/setup-folder-structure/samples/editions-index.js +3 -0
  102. package/build/setup-folder-structure/samples/free-sample.json +25 -0
  103. package/build/setup-folder-structure/samples/settings.json +7 -0
  104. package/build/setup-folder-structure/samples/testSetup-sample.js +14 -0
  105. package/build/setup-folder-structure/samples/uat-config-sample.js +7 -2
  106. package/build/setup-folder-structure/setupProject.js +23 -5
  107. package/build/test/core/dataGenerator/__tests__/DataGenerator.test.js +311 -0
  108. package/build/test/core/dataGenerator/__tests__/DataGeneratorHelper.test.js +204 -0
  109. package/build/test/core/dataGenerator/__tests__/DataGeneratorResolution.test.js +126 -0
  110. package/build/test/core/dataGenerator/authenticator/__tests__/Authenticator.test.js +88 -0
  111. package/build/test/core/dataGenerator/authenticator/__tests__/JWTauthenticator.test.js +136 -0
  112. package/build/test/core/dataGenerator/authenticator/__tests__/OAuthAuthenticator.test.js +39 -0
  113. package/build/test/core/dataGenerator/authenticator/__tests__/OrgOAuthAuthenticator.test.js +39 -0
  114. package/build/test/core/playwright/__tests__/spawnInheritChild.test.js +148 -0
  115. package/build/test/core/playwright/__tests__/tagProcessor.test.js +94 -0
  116. package/build/test/core/playwright/__tests__/validateFeature.test.js +72 -0
  117. package/build/test/core/playwright/buildInFixtures/__tests__/caseTimeout.test.js +96 -0
  118. package/build/test/core/playwright/buildInFixtures/__tests__/executionContext.test.js +27 -0
  119. package/build/test/core/playwright/configuration/__tests__/Configuration.test.js +53 -0
  120. package/build/test/core/playwright/helpers/__tests__/additionalProfiles.test.js +45 -0
  121. package/build/test/core/playwright/helpers/__tests__/configFileNameProvider.test.js +75 -0
  122. package/build/test/core/playwright/helpers/__tests__/configPathResolver.test.js +55 -0
  123. package/build/test/core/playwright/helpers/__tests__/customFixturesHelper.test.js +51 -0
  124. package/build/test/core/playwright/helpers/__tests__/failureTagClassifier.test.js +604 -0
  125. package/build/test/core/playwright/helpers/__tests__/fileMutex.test.js +79 -0
  126. package/build/test/core/playwright/helpers/__tests__/getUsers_ListOfActors.test.js +83 -0
  127. package/build/test/core/playwright/helpers/__tests__/logCollector.test.js +163 -0
  128. package/build/test/core/playwright/helpers/__tests__/runtimeLogReader.test.js +112 -0
  129. package/build/test/core/playwright/helpers/auth/__tests__/checkAuthCookies.test.js +92 -0
  130. package/build/test/core/playwright/runner/__tests__/RunnerHelper.test.js +19 -0
  131. package/build/test/core/playwright/runner/__tests__/SpawnRunner.test.js +30 -0
  132. package/build/utils/cliArgsToObject.js +3 -0
  133. package/build/utils/commonUtils.js +20 -0
  134. package/build/utils/fileUtils.js +43 -1
  135. package/build/utils/logger.js +1 -30
  136. package/changelog.md +23 -0
  137. package/docs/CODE-REVIEW-AGENT.md +399 -0
  138. package/docs/code-review-instructions.md +157 -0
  139. package/jest.config.js +29 -11
  140. package/npm-shrinkwrap.json +14994 -4793
  141. package/package.json +39 -28
  142. package/playwright.config.js +13 -57
  143. package/unit_reports/unit-report.html +277 -0
  144. package/build/bdd-framework/cli/commands/env.js +0 -42
  145. package/build/bdd-framework/cli/commands/export.js +0 -47
  146. package/build/bdd-framework/cli/commands/test.js +0 -64
  147. package/build/bdd-framework/cli/index.js +0 -11
  148. package/build/bdd-framework/cli/options.js +0 -19
  149. package/build/bdd-framework/cli/worker.js +0 -13
  150. package/build/bdd-framework/config/configDir.js +0 -35
  151. package/build/bdd-framework/config/enrichReporterData.js +0 -23
  152. package/build/bdd-framework/config/env.js +0 -50
  153. package/build/bdd-framework/config/index.js +0 -94
  154. package/build/bdd-framework/config/lang.js +0 -14
  155. package/build/bdd-framework/cucumber/buildStepDefinition.js +0 -43
  156. package/build/bdd-framework/cucumber/createTestStep.js +0 -43
  157. package/build/bdd-framework/cucumber/formatter/EventDataCollector.js +0 -126
  158. package/build/bdd-framework/cucumber/formatter/GherkinDocumentParser.js +0 -72
  159. package/build/bdd-framework/cucumber/formatter/PickleParser.js +0 -25
  160. package/build/bdd-framework/cucumber/formatter/durationHelpers.js +0 -13
  161. package/build/bdd-framework/cucumber/formatter/getColorFns.js +0 -57
  162. package/build/bdd-framework/cucumber/formatter/index.js +0 -16
  163. package/build/bdd-framework/cucumber/formatter/locationHelpers.js +0 -16
  164. package/build/bdd-framework/cucumber/loadConfig.js +0 -17
  165. package/build/bdd-framework/cucumber/loadFeatures.js +0 -70
  166. package/build/bdd-framework/cucumber/loadSnippetBuilder.js +0 -20
  167. package/build/bdd-framework/cucumber/loadSteps.js +0 -47
  168. package/build/bdd-framework/cucumber/resolveFeaturePaths.js +0 -62
  169. package/build/bdd-framework/cucumber/stepArguments.js +0 -21
  170. package/build/bdd-framework/cucumber/types.js +0 -5
  171. package/build/bdd-framework/cucumber/valueChecker.js +0 -23
  172. package/build/bdd-framework/decorators.js +0 -18
  173. package/build/bdd-framework/gen/fixtures.js +0 -48
  174. package/build/bdd-framework/gen/formatter.js +0 -123
  175. package/build/bdd-framework/gen/i18n.js +0 -39
  176. package/build/bdd-framework/gen/index.js +0 -185
  177. package/build/bdd-framework/gen/testFile.js +0 -465
  178. package/build/bdd-framework/gen/testMeta.js +0 -60
  179. package/build/bdd-framework/gen/testNode.js +0 -60
  180. package/build/bdd-framework/gen/testPoms.js +0 -133
  181. package/build/bdd-framework/hooks/scenario.js +0 -130
  182. package/build/bdd-framework/hooks/worker.js +0 -89
  183. package/build/bdd-framework/index.js +0 -52
  184. package/build/bdd-framework/playwright/fixtureParameterNames.js +0 -93
  185. package/build/bdd-framework/playwright/getLocationInFile.js +0 -79
  186. package/build/bdd-framework/playwright/loadConfig.js +0 -42
  187. package/build/bdd-framework/playwright/loadUtils.js +0 -33
  188. package/build/bdd-framework/playwright/testTypeImpl.js +0 -61
  189. package/build/bdd-framework/playwright/transform.js +0 -88
  190. package/build/bdd-framework/playwright/types.js +0 -5
  191. package/build/bdd-framework/playwright/utils.js +0 -34
  192. package/build/bdd-framework/reporter/cucumber/base.js +0 -57
  193. package/build/bdd-framework/reporter/cucumber/custom.js +0 -73
  194. package/build/bdd-framework/reporter/cucumber/helper.js +0 -12
  195. package/build/bdd-framework/reporter/cucumber/html.js +0 -35
  196. package/build/bdd-framework/reporter/cucumber/index.js +0 -74
  197. package/build/bdd-framework/reporter/cucumber/json.js +0 -312
  198. package/build/bdd-framework/reporter/cucumber/junit.js +0 -205
  199. package/build/bdd-framework/reporter/cucumber/message.js +0 -20
  200. package/build/bdd-framework/reporter/cucumber/messagesBuilder/AttachmentMapper.js +0 -64
  201. package/build/bdd-framework/reporter/cucumber/messagesBuilder/Builder.js +0 -196
  202. package/build/bdd-framework/reporter/cucumber/messagesBuilder/GherkinDocument.js +0 -43
  203. package/build/bdd-framework/reporter/cucumber/messagesBuilder/GherkinDocumentClone.js +0 -52
  204. package/build/bdd-framework/reporter/cucumber/messagesBuilder/GherkinDocuments.js +0 -105
  205. package/build/bdd-framework/reporter/cucumber/messagesBuilder/Hook.js +0 -70
  206. package/build/bdd-framework/reporter/cucumber/messagesBuilder/Meta.js +0 -45
  207. package/build/bdd-framework/reporter/cucumber/messagesBuilder/Pickles.js +0 -27
  208. package/build/bdd-framework/reporter/cucumber/messagesBuilder/Projects.js +0 -38
  209. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestCase.js +0 -128
  210. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestCaseRun.js +0 -126
  211. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestCaseRunHooks.js +0 -102
  212. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestStepAttachments.js +0 -50
  213. package/build/bdd-framework/reporter/cucumber/messagesBuilder/TestStepRun.js +0 -88
  214. package/build/bdd-framework/reporter/cucumber/messagesBuilder/index.js +0 -30
  215. package/build/bdd-framework/reporter/cucumber/messagesBuilder/pwUtils.js +0 -51
  216. package/build/bdd-framework/reporter/cucumber/messagesBuilder/timing.js +0 -35
  217. package/build/bdd-framework/reporter/cucumber/messagesBuilder/types.js +0 -5
  218. package/build/bdd-framework/run/StepInvoker.js +0 -68
  219. package/build/bdd-framework/run/bddDataAttachment.js +0 -46
  220. package/build/bdd-framework/run/bddFixtures.js +0 -191
  221. package/build/bdd-framework/run/bddWorld.js +0 -79
  222. package/build/bdd-framework/run/bddWorldInternal.js +0 -15
  223. package/build/bdd-framework/snippets/index.js +0 -132
  224. package/build/bdd-framework/snippets/snippetSyntax.js +0 -43
  225. package/build/bdd-framework/snippets/snippetSyntaxDecorators.js +0 -26
  226. package/build/bdd-framework/snippets/snippetSyntaxTs.js +0 -18
  227. package/build/bdd-framework/stepDefinitions/createBdd.js +0 -66
  228. package/build/bdd-framework/stepDefinitions/decorators/class.js +0 -68
  229. package/build/bdd-framework/stepDefinitions/decorators/steps.js +0 -99
  230. package/build/bdd-framework/stepDefinitions/defineStep.js +0 -62
  231. package/build/bdd-framework/stepDefinitions/stepConfig.js +0 -24
  232. package/build/bdd-framework/utils/AutofillMap.js +0 -20
  233. package/build/bdd-framework/utils/exit.js +0 -62
  234. package/build/bdd-framework/utils/index.js +0 -93
  235. package/build/bdd-framework/utils/jsStringWrap.js +0 -44
  236. package/build/bdd-framework/utils/logger.js +0 -30
  237. package/build/bdd-framework/utils/stripAnsiEscapes.js +0 -20
  238. package/build/core/playwright/report-generator.js +0 -42
  239. package/build/core/playwright/tag-processor.js +0 -68
  240. package/build/setup-folder-structure/samples/authUsers-sample.json +0 -9
  241. package/build/setup-folder-structure/samples/env-config-sample.json +0 -21
@@ -0,0 +1,604 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _fs = _interopRequireDefault(require("fs"));
5
+ var _os = _interopRequireDefault(require("os"));
6
+ var _path = _interopRequireDefault(require("path"));
7
+ var toolPatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/toolPatterns"));
8
+ var casePatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/casePatterns"));
9
+ var codePatterns = _interopRequireWildcard(require("../../../../../core/playwright/patterns/codePatterns"));
10
+ var _readConfigFile = require("../../../../../core/playwright/readConfigFile");
11
+ var _runtimeLogPath = require("../../../../../core/playwright/helpers/runtimeLogPath");
12
+ var _failureTagClassifier = require("../../../../../core/playwright/helpers/failureTagClassifier");
13
+ var _runtimeLogAnnotator = require("../../../../../core/playwright/helpers/runtimeLogAnnotator");
14
+ var _failureTagConstants = require("../../../../../core/playwright/constants/failureTagConstants");
15
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
16
+ jest.mock('../../../../../core/playwright/readConfigFile', () => ({
17
+ generateConfigFromFile: jest.fn()
18
+ }));
19
+ const TEST_TITLE = 'environment-abort-test';
20
+ let tempDir;
21
+ function runtimeLogsDir() {
22
+ return _path.default.join(tempDir, 'runtime-logs');
23
+ }
24
+ function writeRuntimeLog(events, {
25
+ workerIndex = 0,
26
+ retry = 0,
27
+ title = TEST_TITLE
28
+ } = {}) {
29
+ const file = (0, _runtimeLogPath.resolveRuntimeLogFilePath)(runtimeLogsDir(), {
30
+ workerIndex,
31
+ retry,
32
+ title
33
+ });
34
+ _fs.default.mkdirSync(_path.default.dirname(file), {
35
+ recursive: true
36
+ });
37
+ _fs.default.writeFileSync(file, `${events.map(e => JSON.stringify(e)).join('\n')}\n`);
38
+ return file;
39
+ }
40
+ function failingDetail(events, options = {}) {
41
+ writeRuntimeLog(events, options);
42
+ return {
43
+ status: 'unexpected',
44
+ results: [{
45
+ status: 'failed',
46
+ workerIndex: options.workerIndex ?? 0,
47
+ retry: options.retry ?? 0
48
+ }]
49
+ };
50
+ }
51
+ function failingDetailWithStdio({
52
+ stdout = [],
53
+ stderr = []
54
+ }) {
55
+ return {
56
+ status: 'unexpected',
57
+ results: [{
58
+ status: 'failed',
59
+ attachments: [],
60
+ stdout,
61
+ stderr
62
+ }]
63
+ };
64
+ }
65
+ beforeEach(() => {
66
+ tempDir = _fs.default.mkdtempSync(_path.default.join(_os.default.tmpdir(), 'failure-tag-'));
67
+ _readConfigFile.generateConfigFromFile.mockReturnValue({
68
+ reportPath: tempDir
69
+ });
70
+ });
71
+ afterEach(() => {
72
+ _fs.default.rmSync(tempDir, {
73
+ recursive: true,
74
+ force: true
75
+ });
76
+ });
77
+ describe('classifyFailureTags', () => {
78
+ test('leaves retry-passed (flaky) outcomes untagged', () => {
79
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([{
80
+ status: 'flaky',
81
+ results: []
82
+ }], TEST_TITLE);
83
+ expect(tags).toEqual([]);
84
+ });
85
+ test('tags environment when a document abort is present', () => {
86
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
87
+ kind: 'requestfailed',
88
+ url: 'https://app/events/list/commentsubtabevent',
89
+ failure: 'net::ERR_ABORTED',
90
+ resourceType: 'document'
91
+ }])], TEST_TITLE);
92
+ expect(tags).toEqual(['ENVIRONMENT']);
93
+ });
94
+ test('tags environment for a network/cert failure', () => {
95
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
96
+ kind: 'requestfailed',
97
+ url: 'https://js/gshandler.js',
98
+ failure: 'net::ERR_CERT_COMMON_NAME_INVALID',
99
+ resourceType: 'script'
100
+ }], {
101
+ title: 'cert-test'
102
+ })], 'cert-test');
103
+ expect(tags).toEqual(['ENVIRONMENT']);
104
+ });
105
+ test('tags code when an app undefined-property error is present', () => {
106
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
107
+ kind: 'pageerror',
108
+ name: 'TypeError',
109
+ message: "Cannot read properties of undefined (reading 'id')"
110
+ }], {
111
+ title: 'code-test'
112
+ })], 'code-test');
113
+ expect(tags).toEqual(['CODE']);
114
+ });
115
+ test('defaults to code (not environment) for a non-document xhr abort', () => {
116
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
117
+ kind: 'requestfailed',
118
+ url: 'https://app/api/tickets',
119
+ failure: 'net::ERR_ABORTED',
120
+ resourceType: 'xhr'
121
+ }], {
122
+ title: 'xhr-test'
123
+ })], 'xhr-test');
124
+ expect(tags).toEqual(['CODE']);
125
+ });
126
+ test('tags case when stderr reports DATA_GENERATION_ERROR', () => {
127
+ const detail = failingDetailWithStdio({
128
+ stderr: [{
129
+ text: 'status : DATA_GENERATION_ERROR\n generator: comments'
130
+ }]
131
+ });
132
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
133
+ expect(tags).toEqual(['CASE']);
134
+ });
135
+ test('tags tool for a known tool pattern in base64 stdout', () => {
136
+ const detail = failingDetailWithStdio({
137
+ stdout: [{
138
+ buffer: Buffer.from('Error during login').toString('base64')
139
+ }]
140
+ });
141
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
142
+ expect(tags).toEqual(['TOOL']);
143
+ });
144
+ test('tags case when stderr matches a case pattern', () => {
145
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
146
+ try {
147
+ casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
148
+ const detail = failingDetailWithStdio({
149
+ stderr: [{
150
+ text: 'Error: expect(received).toBe(expected)'
151
+ }]
152
+ });
153
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
154
+ expect(tags).toEqual(['CASE']);
155
+ } finally {
156
+ casePatterns.CASE_PATTERNS.length = 0;
157
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
158
+ }
159
+ });
160
+ test('emits only the highest-priority tag (CODE) when CODE and ENVIRONMENT both match', () => {
161
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
162
+ kind: 'requestfailed',
163
+ url: 'https://app/events/list/commentsubtabevent',
164
+ failure: 'net::ERR_ABORTED',
165
+ resourceType: 'document'
166
+ }, {
167
+ kind: 'pageerror',
168
+ name: 'TypeError',
169
+ message: "Cannot read properties of undefined (reading 'id')"
170
+ }], {
171
+ title: 'code-over-env-test'
172
+ })], 'code-over-env-test');
173
+ expect(tags).toEqual(['CODE']);
174
+ });
175
+ test('defaults to code (not tool) for benign stdout/stderr', () => {
176
+ const detail = failingDetailWithStdio({
177
+ stdout: [{
178
+ text: 'Generated 5 records successfully'
179
+ }],
180
+ stderr: []
181
+ });
182
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
183
+ expect(tags).toEqual(['CODE']);
184
+ });
185
+ test('does not throw for malformed stdio chunk entries', () => {
186
+ const detail = failingDetailWithStdio({
187
+ stdout: [null, undefined, 42, {
188
+ text: 'still fine'
189
+ }],
190
+ stderr: [false]
191
+ });
192
+ expect(() => (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE)).not.toThrow();
193
+ expect((0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE)).toEqual(['CODE']);
194
+ });
195
+ test('defaults to code when a failure matches no pattern', () => {
196
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([failingDetail([{
197
+ kind: 'response',
198
+ status: 200,
199
+ method: 'GET',
200
+ url: 'https://app/ok'
201
+ }, {
202
+ kind: 'console',
203
+ level: 'error',
204
+ text: 'Some benign log line',
205
+ location: {
206
+ url: 'https://app/page'
207
+ }
208
+ }], {
209
+ title: 'neutral-test'
210
+ })], 'neutral-test');
211
+ expect(tags).toEqual(['CODE']);
212
+ });
213
+ test('leaves a passing spec untagged (no default code)', () => {
214
+ const tags = (0, _failureTagClassifier.classifyFailureTags)([{
215
+ status: 'expected',
216
+ results: [{
217
+ status: 'passed',
218
+ workerIndex: 0,
219
+ retry: 0
220
+ }]
221
+ }], 'passing-test');
222
+ expect(tags).toEqual([]);
223
+ });
224
+ });
225
+ describe('classifyFailureDetails', () => {
226
+ test('records the matching pattern for environment failures', () => {
227
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetail([{
228
+ kind: 'requestfailed',
229
+ url: 'https://app/events/list/commentsubtabevent',
230
+ failure: 'net::ERR_ABORTED',
231
+ resourceType: 'document'
232
+ }])], TEST_TITLE);
233
+ expect(details.tags).toEqual(['ENVIRONMENT']);
234
+ expect(details.matches).toHaveLength(1);
235
+ expect(details.matches[0]).toMatchObject({
236
+ tag: 'ENVIRONMENT',
237
+ pattern: '^requestfailed document\\b[\\s\\S]*ERR_ABORTED',
238
+ source: 'runtime-log'
239
+ });
240
+ expect(details.matches[0].text).toContain('ERR_ABORTED');
241
+ });
242
+ test('records the matching pattern for code failures from events', () => {
243
+ const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([{
244
+ kind: 'pageerror',
245
+ name: 'TypeError',
246
+ message: "Cannot read properties of undefined (reading 'id')"
247
+ }]);
248
+ expect(details.tags).toEqual(['CODE']);
249
+ expect(details.matches[0]).toMatchObject({
250
+ tag: 'CODE',
251
+ pattern: 'Cannot read properties of (undefined|null)',
252
+ source: 'runtime-log'
253
+ });
254
+ });
255
+ test('defaults to code with no matches when events carry no recognised pattern', () => {
256
+ const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([]);
257
+ expect(details.tags).toEqual(['CODE']);
258
+ expect(details.matches).toEqual([]);
259
+ });
260
+ test('tags tool when stdio text has Data Generator Service Error: status: 404', () => {
261
+ const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([], 'Error Message: Data Generator Service Error: status: 404, body: ');
262
+ expect(details.tags).toEqual(['TOOL']);
263
+ expect(details.matches[0]).toMatchObject({
264
+ tag: 'TOOL',
265
+ pattern: 'Data Generator Service Error: status: 404',
266
+ source: 'stdio'
267
+ });
268
+ });
269
+ test('records tool pattern from stdio', () => {
270
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetailWithStdio({
271
+ stderr: [{
272
+ text: 'Error during login for actor admin'
273
+ }]
274
+ })], TEST_TITLE);
275
+ expect(details.tags).toEqual(['TOOL']);
276
+ expect(details.matches[0]).toMatchObject({
277
+ tag: 'TOOL',
278
+ pattern: 'Error during login',
279
+ source: 'stdio'
280
+ });
281
+ });
282
+ test('records case pattern from stdio without implying code', () => {
283
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
284
+ try {
285
+ casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
286
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetailWithStdio({
287
+ stderr: [{
288
+ text: 'Error: expect(received).toBe(expected)'
289
+ }]
290
+ })], TEST_TITLE);
291
+ expect(details.tags).toEqual(['CASE']);
292
+ expect(details.matches).toHaveLength(1);
293
+ expect(details.matches[0]).toMatchObject({
294
+ tag: 'CASE',
295
+ pattern: 'expect\\(received\\)\\.toBe',
296
+ source: 'stdio'
297
+ });
298
+ } finally {
299
+ casePatterns.CASE_PATTERNS.length = 0;
300
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
301
+ }
302
+ });
303
+ test('resolves to CASE when a stdio case match outranks a runtime-log code match', () => {
304
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
305
+ try {
306
+ casePatterns.CASE_PATTERNS.push(/expect\(received\)\.toBe/i);
307
+ writeRuntimeLog([{
308
+ kind: 'pageerror',
309
+ name: 'TypeError',
310
+ message: "Cannot read properties of undefined (reading 'id')"
311
+ }], {
312
+ title: 'case-with-runtime-code'
313
+ });
314
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([{
315
+ status: 'unexpected',
316
+ results: [{
317
+ status: 'failed',
318
+ workerIndex: 0,
319
+ retry: 0,
320
+ stderr: [{
321
+ text: 'Error: expect(received).toBe(expected)'
322
+ }]
323
+ }]
324
+ }], 'case-with-runtime-code');
325
+
326
+ // CASE outranks CODE, so only CASE surfaces as the tag and as matches.
327
+ expect(details.tags).toEqual(['CASE']);
328
+ expect(details.matches).toHaveLength(1);
329
+ expect(details.matches.find(match => match.tag === 'CODE')).toBeUndefined();
330
+ expect(details.matches[0]).toMatchObject({
331
+ tag: 'CASE',
332
+ source: 'stdio',
333
+ pattern: 'expect\\(received\\)\\.toBe'
334
+ });
335
+ } finally {
336
+ casePatterns.CASE_PATTERNS.length = 0;
337
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
338
+ }
339
+ });
340
+ test('keeps case as the tag and does not add code when no runtime log is present', () => {
341
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
342
+ try {
343
+ casePatterns.CASE_PATTERNS.push(/Timed out \d+ms waiting for/i);
344
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetailWithStdio({
345
+ stderr: [{
346
+ text: 'Timed out 5000ms waiting for locator("#save")'
347
+ }]
348
+ })], TEST_TITLE);
349
+ expect(details.tags).toEqual(['CASE']);
350
+ expect(details.matches.find(match => match.tag === 'CODE')).toBeUndefined();
351
+ } finally {
352
+ casePatterns.CASE_PATTERNS.length = 0;
353
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
354
+ }
355
+ });
356
+ test('records every CODE pattern hit from the runtime log', () => {
357
+ const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([{
358
+ kind: 'pageerror',
359
+ name: 'TypeError',
360
+ message: "Cannot read properties of undefined (reading 'id')"
361
+ }, {
362
+ kind: 'pageerror',
363
+ name: 'TypeError',
364
+ message: 'foo is not a function'
365
+ }, {
366
+ kind: 'console',
367
+ level: 'error',
368
+ text: 'bar is not defined',
369
+ location: {
370
+ url: 'https://app/page'
371
+ }
372
+ }]);
373
+ expect(details.tags).toEqual(['CODE']);
374
+ expect(details.matches.filter(match => match.tag === 'CODE')).toHaveLength(3);
375
+ expect(details.matches.map(match => match.pattern)).toEqual(expect.arrayContaining(['Cannot read properties of (undefined|null)', '\\bis not a function\\b', '\\bis not defined\\b']));
376
+ });
377
+ test('keeps only winning CODE matches when CODE and ENVIRONMENT both match', () => {
378
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([failingDetail([{
379
+ kind: 'requestfailed',
380
+ url: 'https://app/events/list/commentsubtabevent',
381
+ failure: 'net::ERR_ABORTED',
382
+ resourceType: 'document'
383
+ }, {
384
+ kind: 'requestfailed',
385
+ url: 'https://cdn/app.js',
386
+ failure: 'net::ERR_CONNECTION_REFUSED',
387
+ resourceType: 'script'
388
+ }, {
389
+ kind: 'pageerror',
390
+ name: 'TypeError',
391
+ message: "Cannot read properties of undefined (reading 'id')"
392
+ }, {
393
+ kind: 'pageerror',
394
+ name: 'TypeError',
395
+ message: 'handler is not a function'
396
+ }], {
397
+ title: 'multi-code-env-test'
398
+ })], 'multi-code-env-test');
399
+ expect(details.tags).toEqual(['CODE']);
400
+ expect(details.matches).toHaveLength(2);
401
+ expect(details.matches.every(match => match.tag === 'CODE')).toBe(true);
402
+ expect(details.matches.find(match => match.tag === 'ENVIRONMENT')).toBeUndefined();
403
+ });
404
+ test('dedupes identical winning-category matches', () => {
405
+ const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([{
406
+ kind: 'pageerror',
407
+ name: 'TypeError',
408
+ message: "Cannot read properties of undefined (reading 'id')"
409
+ }, {
410
+ kind: 'pageerror',
411
+ name: 'TypeError',
412
+ message: "Cannot read properties of undefined (reading 'id')"
413
+ }, {
414
+ kind: 'pageerror',
415
+ name: 'TypeError',
416
+ message: "Cannot read properties of undefined (reading 'id')"
417
+ }, {
418
+ kind: 'pageerror',
419
+ name: 'TypeError',
420
+ message: 'a is not a function'
421
+ }, {
422
+ kind: 'pageerror',
423
+ name: 'TypeError',
424
+ message: 'e.apply is not a function'
425
+ }, {
426
+ kind: 'pageerror',
427
+ name: 'TypeError',
428
+ message: 'a is not a function'
429
+ }]);
430
+ expect(details.tags).toEqual(['CODE']);
431
+ expect(details.matches).toHaveLength(3);
432
+ expect(details.matches.map(match => match.text)).toEqual(["pageerror TypeError Cannot read properties of undefined (reading 'id')", 'pageerror TypeError a is not a function', 'pageerror TypeError e.apply is not a function']);
433
+ });
434
+ test('dedupes annotated dual-match JSONL lines before classifying from disk', () => {
435
+ writeRuntimeLog([{
436
+ pattern: 'CODE',
437
+ kind: 'pageerror',
438
+ name: 'TypeError',
439
+ message: "Cannot read properties of undefined (reading 'id')"
440
+ }, {
441
+ pattern: 'ENVIRONMENT',
442
+ kind: 'pageerror',
443
+ name: 'TypeError',
444
+ message: "Cannot read properties of undefined (reading 'id')"
445
+ }], {
446
+ title: 'annotated-dual-test'
447
+ });
448
+ const details = (0, _failureTagClassifier.classifyFailureDetails)([{
449
+ status: 'unexpected',
450
+ results: [{
451
+ status: 'failed',
452
+ workerIndex: 0,
453
+ retry: 0
454
+ }]
455
+ }], 'annotated-dual-test');
456
+ expect(details.tags).toEqual(['CODE']);
457
+ expect(details.matches.filter(match => match.tag === 'CODE')).toHaveLength(1);
458
+ });
459
+ });
460
+ describe('annotateRuntimeLogEvents', () => {
461
+ test('adds pattern CODE on matching events and leaves unmatched events unchanged', () => {
462
+ const lines = (0, _runtimeLogAnnotator.annotateRuntimeLogEvents)([{
463
+ ts: 1,
464
+ kind: 'pageerror',
465
+ name: 'TypeError',
466
+ message: "Cannot read properties of undefined (reading 'id')"
467
+ }, {
468
+ ts: 2,
469
+ kind: 'console',
470
+ level: 'error',
471
+ text: 'Some benign log line',
472
+ location: {
473
+ url: 'https://app/page'
474
+ }
475
+ }]);
476
+ expect(lines).toHaveLength(2);
477
+ expect(lines[0]).toMatchObject({
478
+ pattern: 'CODE',
479
+ ts: 1,
480
+ kind: 'pageerror'
481
+ });
482
+ expect(lines[1]).toEqual({
483
+ ts: 2,
484
+ kind: 'console',
485
+ level: 'error',
486
+ text: 'Some benign log line',
487
+ location: {
488
+ url: 'https://app/page'
489
+ }
490
+ });
491
+ expect(lines[1].pattern).toBeUndefined();
492
+ });
493
+ test('emits one line per matched category when an event hits CODE and ENVIRONMENT', () => {
494
+ const originalCode = codePatterns.CODE_PATTERNS.slice();
495
+ try {
496
+ // Force a dual match on one environment event by also treating ERR_ABORTED as CODE.
497
+ codePatterns.CODE_PATTERNS.push(/ERR_ABORTED/i);
498
+ const event = {
499
+ ts: 1783961014561,
500
+ kind: 'requestfailed',
501
+ url: 'https://app/events/list/commentsubtabevent',
502
+ failure: 'net::ERR_ABORTED',
503
+ resourceType: 'document',
504
+ method: 'GET'
505
+ };
506
+ const lines = (0, _runtimeLogAnnotator.annotateRuntimeLogEvents)([event]);
507
+ expect(lines).toHaveLength(2);
508
+ expect(lines.map(line => line.pattern).sort()).toEqual(['CODE', 'ENVIRONMENT']);
509
+ expect(lines[0].ts).toBe(1783961014561);
510
+ expect(lines[1].ts).toBe(1783961014561);
511
+ } finally {
512
+ codePatterns.CODE_PATTERNS.length = 0;
513
+ codePatterns.CODE_PATTERNS.push(...originalCode);
514
+ }
515
+ });
516
+ });
517
+ describe('failure tag metadata attachment', () => {
518
+ test('builds a small JSON attachment when tags are present', () => {
519
+ const details = (0, _failureTagClassifier.classifyFailureDetailsFromEvents)([{
520
+ kind: 'pageerror',
521
+ name: 'TypeError',
522
+ message: "Cannot read properties of undefined (reading 'id')"
523
+ }]);
524
+ const attachment = (0, _failureTagClassifier.buildFailureTagMetadataAttachment)(details);
525
+ expect(attachment).toMatchObject({
526
+ name: _failureTagConstants.FAILURE_TAG_METADATA_ATTACHMENT,
527
+ contentType: 'application/json'
528
+ });
529
+ expect(JSON.parse(attachment.body.toString('utf8'))).toEqual(details);
530
+ });
531
+ test('returns null when no tags are present', () => {
532
+ expect((0, _failureTagClassifier.buildFailureTagMetadataAttachment)({
533
+ tags: [],
534
+ matches: []
535
+ })).toBeNull();
536
+ });
537
+ test('replaces an existing metadata attachment', () => {
538
+ const first = (0, _failureTagClassifier.buildFailureTagMetadataAttachment)({
539
+ tags: ['CODE'],
540
+ matches: [{
541
+ tag: 'CODE',
542
+ pattern: 'old',
543
+ source: 'runtime-log',
544
+ text: 'old'
545
+ }]
546
+ });
547
+ const second = (0, _failureTagClassifier.buildFailureTagMetadataAttachment)({
548
+ tags: ['TOOL'],
549
+ matches: [{
550
+ tag: 'TOOL',
551
+ pattern: 'new',
552
+ source: 'stdio',
553
+ text: 'new'
554
+ }]
555
+ });
556
+ const merged = (0, _failureTagClassifier.upsertFailureTagMetadataAttachment)([{
557
+ name: 'screenshot',
558
+ path: '/tmp/a.png'
559
+ }, first], JSON.parse(second.body.toString('utf8')));
560
+ expect(merged).toHaveLength(2);
561
+ expect(merged[0].name).toBe('screenshot');
562
+ expect(merged[1].name).toBe(_failureTagConstants.FAILURE_TAG_METADATA_ATTACHMENT);
563
+ expect(JSON.parse(merged[1].body.toString('utf8')).matches[0].pattern).toBe('new');
564
+ });
565
+ });
566
+ describe('mergeFailureTags', () => {
567
+ test('places suffixed failure tags first, then existing tags', () => {
568
+ expect((0, _failureTagClassifier.mergeFailureTags)(['@comments', '@calls'], ['CODE', 'ENVIRONMENT'])).toEqual(['CODE_failure', 'ENVIRONMENT_failure', '@comments', '@calls']);
569
+ });
570
+ test('formats all failure tags with suffix', () => {
571
+ expect((0, _failureTagClassifier.mergeFailureTags)([], ['CODE', 'TOOL'])).toEqual(['CODE_failure', 'TOOL_failure']);
572
+ });
573
+ test('does not duplicate an already-present failure tag', () => {
574
+ expect((0, _failureTagClassifier.mergeFailureTags)(['CODE_failure'], ['CODE'])).toEqual(['CODE_failure']);
575
+ });
576
+ test('formats case and code failure tags with suffix', () => {
577
+ expect((0, _failureTagClassifier.mergeFailureTags)(['@smoke'], ['CASE', 'CODE'])).toEqual(['CASE_failure', 'CODE_failure', '@smoke']);
578
+ });
579
+ });
580
+ describe('regex safety', () => {
581
+ test('resets stateful regex for tool pattern checks', () => {
582
+ const originalToolPatterns = toolPatterns.TOOL_PATTERNS.slice();
583
+ const originalCasePatterns = casePatterns.CASE_PATTERNS.slice();
584
+ try {
585
+ toolPatterns.TOOL_PATTERNS.length = 0;
586
+ toolPatterns.TOOL_PATTERNS.push(/DATA_GENERATION_ERROR/g);
587
+ casePatterns.CASE_PATTERNS.length = 0;
588
+ const detail = failingDetailWithStdio({
589
+ stderr: [{
590
+ text: 'status : DATA_GENERATION_ERROR'
591
+ }]
592
+ });
593
+ const first = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
594
+ const second = (0, _failureTagClassifier.classifyFailureTags)([detail], TEST_TITLE);
595
+ expect(first).toEqual(['TOOL']);
596
+ expect(second).toEqual(['TOOL']);
597
+ } finally {
598
+ toolPatterns.TOOL_PATTERNS.length = 0;
599
+ toolPatterns.TOOL_PATTERNS.push(...originalToolPatterns);
600
+ casePatterns.CASE_PATTERNS.length = 0;
601
+ casePatterns.CASE_PATTERNS.push(...originalCasePatterns);
602
+ }
603
+ });
604
+ });
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ var _fileMutex = _interopRequireDefault(require("../../../../../core/playwright/helpers/fileMutex"));
5
+ var _path = _interopRequireDefault(require("path"));
6
+ var _fs = require("fs");
7
+ jest.mock('fs');
8
+ describe('FileMutex', () => {
9
+ const directory = '/tmp/locks';
10
+ const lockFileName = 'test-lock';
11
+ const fileDeletionTimeoutConfig = {
12
+ timeout: 1000
13
+ };
14
+ const lockFilePath = _path.default.resolve(directory, lockFileName + '.lock');
15
+ let fileMutex;
16
+ beforeEach(() => {
17
+ jest.clearAllMocks();
18
+ fileMutex = new _fileMutex.default(directory, lockFileName, fileDeletionTimeoutConfig);
19
+ });
20
+ describe('acquire', () => {
21
+ it('should create the lock file if it does not exist', async () => {
22
+ _fs.writeFileSync.mockImplementation(() => {});
23
+ await fileMutex.acquire();
24
+ expect(_fs.writeFileSync).toHaveBeenCalledWith(fileMutex.lockFilePath, 'locked');
25
+ });
26
+ it('should wait for lock file deletion if it exists', async () => {
27
+ _fs.existsSync.mockImplementation(filePath => filePath === fileMutex.lockFilePath);
28
+ _fs.watch.mockImplementation((dir, callback) => {
29
+ setTimeout(() => {
30
+ _fs.existsSync.mockImplementation(() => false);
31
+ callback('rename', fileMutex.lockFileName);
32
+ }, fileDeletionTimeoutConfig);
33
+ return {
34
+ close: jest.fn()
35
+ };
36
+ });
37
+ await fileMutex.acquire();
38
+ expect(_fs.watch).toHaveBeenCalledWith(directory, expect.any(Function));
39
+ });
40
+ it('should reject if watch timeout exceeds', async () => {
41
+ _fs.existsSync.mockImplementation(filePath => filePath === lockFilePath);
42
+ _fs.watch.mockImplementation(() => {
43
+ return {
44
+ close: jest.fn()
45
+ };
46
+ });
47
+ await expect(fileMutex.acquire()).rejects.toThrow('Watch timeout exceeded');
48
+ });
49
+ });
50
+ describe('release', () => {
51
+ it('should delete the lock file if it exists', async () => {
52
+ _fs.existsSync.mockReturnValue(true);
53
+ _fs.unlinkSync.mockImplementation(() => {});
54
+ await fileMutex.release();
55
+ expect(_fs.existsSync).toHaveBeenCalledWith(lockFilePath);
56
+ expect(_fs.unlinkSync).toHaveBeenCalledWith(lockFilePath);
57
+ });
58
+ it('should release lock by deleting lock file', async () => {
59
+ _fs.existsSync.mockReturnValue(true);
60
+ _fs.unlinkSync.mockImplementation(() => {});
61
+ await fileMutex.release();
62
+ expect(_fs.unlinkSync).toHaveBeenCalledWith(lockFilePath);
63
+ });
64
+ it('should not attempt to delete the lock file if it does not exist', async () => {
65
+ _fs.existsSync.mockReturnValue(false);
66
+ await fileMutex.release();
67
+ expect(_fs.existsSync).toHaveBeenCalledWith(lockFilePath);
68
+ expect(_fs.unlinkSync).not.toHaveBeenCalled();
69
+ });
70
+ it('should log an error if deleting the lock file fails', async () => {
71
+ const errorMessage = 'Error deleting lock file';
72
+ _fs.existsSync.mockReturnValue(true);
73
+ _fs.unlinkSync.mockImplementation(() => {
74
+ throw new Error(errorMessage);
75
+ });
76
+ await expect(fileMutex.release()).rejects.toThrow(errorMessage);
77
+ });
78
+ });
79
+ });