@urateam/core 0.1.32 → 0.1.34

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 (132) hide show
  1. package/dist/__tests__/audit-immutability.test.js +50 -1
  2. package/dist/__tests__/audit-immutability.test.js.map +1 -1
  3. package/dist/__tests__/auth-monitor.test.d.ts +2 -0
  4. package/dist/__tests__/auth-monitor.test.d.ts.map +1 -0
  5. package/dist/__tests__/auth-monitor.test.js +253 -0
  6. package/dist/__tests__/auth-monitor.test.js.map +1 -0
  7. package/dist/__tests__/bec-186-repro.test.d.ts +16 -0
  8. package/dist/__tests__/bec-186-repro.test.d.ts.map +1 -0
  9. package/dist/__tests__/bec-186-repro.test.js +223 -0
  10. package/dist/__tests__/bec-186-repro.test.js.map +1 -0
  11. package/dist/__tests__/db-migrations.test.d.ts +2 -0
  12. package/dist/__tests__/db-migrations.test.d.ts.map +1 -0
  13. package/dist/__tests__/db-migrations.test.js +237 -0
  14. package/dist/__tests__/db-migrations.test.js.map +1 -0
  15. package/dist/__tests__/executor-issue-id.test.js +2 -0
  16. package/dist/__tests__/executor-issue-id.test.js.map +1 -1
  17. package/dist/__tests__/pm-scheduler.test.js +59 -0
  18. package/dist/__tests__/pm-scheduler.test.js.map +1 -1
  19. package/dist/__tests__/post-fanout-comments.test.js +36 -0
  20. package/dist/__tests__/post-fanout-comments.test.js.map +1 -1
  21. package/dist/__tests__/preflight-claude-auth.test.d.ts +2 -0
  22. package/dist/__tests__/preflight-claude-auth.test.d.ts.map +1 -0
  23. package/dist/__tests__/preflight-claude-auth.test.js +36 -0
  24. package/dist/__tests__/preflight-claude-auth.test.js.map +1 -0
  25. package/dist/__tests__/resolve-claude-auth.test.d.ts +2 -0
  26. package/dist/__tests__/resolve-claude-auth.test.d.ts.map +1 -0
  27. package/dist/__tests__/resolve-claude-auth.test.js +129 -0
  28. package/dist/__tests__/resolve-claude-auth.test.js.map +1 -0
  29. package/dist/__tests__/runner-retry-strategies.test.d.ts +23 -0
  30. package/dist/__tests__/runner-retry-strategies.test.d.ts.map +1 -0
  31. package/dist/__tests__/runner-retry-strategies.test.js +274 -0
  32. package/dist/__tests__/runner-retry-strategies.test.js.map +1 -0
  33. package/dist/__tests__/scratch-file-guard.test.d.ts +2 -0
  34. package/dist/__tests__/scratch-file-guard.test.d.ts.map +1 -0
  35. package/dist/__tests__/scratch-file-guard.test.js +144 -0
  36. package/dist/__tests__/scratch-file-guard.test.js.map +1 -0
  37. package/dist/__tests__/spec-vs-impl-gate.test.d.ts +2 -0
  38. package/dist/__tests__/spec-vs-impl-gate.test.d.ts.map +1 -0
  39. package/dist/__tests__/spec-vs-impl-gate.test.js +222 -0
  40. package/dist/__tests__/spec-vs-impl-gate.test.js.map +1 -0
  41. package/dist/__tests__/stage-models.test.js +4 -0
  42. package/dist/__tests__/stage-models.test.js.map +1 -1
  43. package/dist/__tests__/typecheck-gate.test.d.ts +2 -0
  44. package/dist/__tests__/typecheck-gate.test.d.ts.map +1 -0
  45. package/dist/__tests__/typecheck-gate.test.js +196 -0
  46. package/dist/__tests__/typecheck-gate.test.js.map +1 -0
  47. package/dist/audit/events.d.ts +52 -0
  48. package/dist/audit/events.d.ts.map +1 -1
  49. package/dist/audit/events.js +81 -0
  50. package/dist/audit/events.js.map +1 -1
  51. package/dist/db/client.d.ts.map +1 -1
  52. package/dist/db/client.js +8 -0
  53. package/dist/db/client.js.map +1 -1
  54. package/dist/db/migrations/postgres/014_missing_indexes.sql +28 -0
  55. package/dist/db/migrations/sqlite/013_missing_indexes.sql +28 -0
  56. package/dist/executor/auth-check.d.ts +39 -0
  57. package/dist/executor/auth-check.d.ts.map +1 -1
  58. package/dist/executor/auth-check.js +31 -0
  59. package/dist/executor/auth-check.js.map +1 -1
  60. package/dist/executor/auth-monitor.d.ts +40 -0
  61. package/dist/executor/auth-monitor.d.ts.map +1 -0
  62. package/dist/executor/auth-monitor.js +114 -0
  63. package/dist/executor/auth-monitor.js.map +1 -0
  64. package/dist/executor/executor.d.ts.map +1 -1
  65. package/dist/executor/executor.js +12 -26
  66. package/dist/executor/executor.js.map +1 -1
  67. package/dist/executor/index.d.ts +2 -0
  68. package/dist/executor/index.d.ts.map +1 -1
  69. package/dist/executor/index.js +2 -0
  70. package/dist/executor/index.js.map +1 -1
  71. package/dist/executor/review/post-fanout-comments.d.ts +8 -0
  72. package/dist/executor/review/post-fanout-comments.d.ts.map +1 -1
  73. package/dist/executor/review/post-fanout-comments.js +23 -3
  74. package/dist/executor/review/post-fanout-comments.js.map +1 -1
  75. package/dist/index.d.ts +2 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +2 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/notifier/composite.d.ts +1 -0
  80. package/dist/notifier/composite.d.ts.map +1 -1
  81. package/dist/notifier/composite.js +3 -0
  82. package/dist/notifier/composite.js.map +1 -1
  83. package/dist/notifier/linear.d.ts +2 -0
  84. package/dist/notifier/linear.d.ts.map +1 -1
  85. package/dist/notifier/linear.js +12 -2
  86. package/dist/notifier/linear.js.map +1 -1
  87. package/dist/pipeline/runner.d.ts.map +1 -1
  88. package/dist/pipeline/runner.js +152 -2
  89. package/dist/pipeline/runner.js.map +1 -1
  90. package/dist/pipeline/scratch-file-guard.d.ts +21 -0
  91. package/dist/pipeline/scratch-file-guard.d.ts.map +1 -0
  92. package/dist/pipeline/scratch-file-guard.js +155 -0
  93. package/dist/pipeline/scratch-file-guard.js.map +1 -0
  94. package/dist/pipeline/spec-vs-impl-gate.d.ts +49 -0
  95. package/dist/pipeline/spec-vs-impl-gate.d.ts.map +1 -0
  96. package/dist/pipeline/spec-vs-impl-gate.js +177 -0
  97. package/dist/pipeline/spec-vs-impl-gate.js.map +1 -0
  98. package/dist/pipeline/typecheck-gate.d.ts +34 -0
  99. package/dist/pipeline/typecheck-gate.d.ts.map +1 -0
  100. package/dist/pipeline/typecheck-gate.js +89 -0
  101. package/dist/pipeline/typecheck-gate.js.map +1 -0
  102. package/dist/pm/scheduler.d.ts.map +1 -1
  103. package/dist/pm/scheduler.js +19 -0
  104. package/dist/pm/scheduler.js.map +1 -1
  105. package/dist/release-manager/index.d.ts +2 -0
  106. package/dist/release-manager/index.d.ts.map +1 -1
  107. package/dist/release-manager/index.js +2 -0
  108. package/dist/release-manager/index.js.map +1 -1
  109. package/dist/release-manager/release-helpers.d.ts +112 -0
  110. package/dist/release-manager/release-helpers.d.ts.map +1 -0
  111. package/dist/release-manager/release-helpers.js +164 -0
  112. package/dist/release-manager/release-helpers.js.map +1 -0
  113. package/dist/release-manager/release-tick.d.ts +101 -0
  114. package/dist/release-manager/release-tick.d.ts.map +1 -0
  115. package/dist/release-manager/release-tick.js +374 -0
  116. package/dist/release-manager/release-tick.js.map +1 -0
  117. package/dist/release-manager/scheduler.d.ts +28 -3
  118. package/dist/release-manager/scheduler.d.ts.map +1 -1
  119. package/dist/release-manager/scheduler.js +41 -417
  120. package/dist/release-manager/scheduler.js.map +1 -1
  121. package/dist/server.d.ts.map +1 -1
  122. package/dist/server.js +3 -0
  123. package/dist/server.js.map +1 -1
  124. package/dist/types.d.ts +15 -0
  125. package/dist/types.d.ts.map +1 -1
  126. package/dist/types.js +17 -0
  127. package/dist/types.js.map +1 -1
  128. package/dist/webhook/github-handler.d.ts +7 -1
  129. package/dist/webhook/github-handler.d.ts.map +1 -1
  130. package/dist/webhook/github-handler.js +82 -38
  131. package/dist/webhook/github-handler.js.map +1 -1
  132. package/package.json +1 -1
@@ -9,7 +9,9 @@
9
9
  */
10
10
  import { describe, it, expect } from "vitest";
11
11
  import { execFileSync } from "node:child_process";
12
+ import { readFileSync } from "node:fs";
12
13
  import path from "node:path";
14
+ import { AuditEventTypeSchema, AuditActorTypeSchema } from "../types.js";
13
15
  describe("audit_events immutability", () => {
14
16
  it("only audit/retention.ts may delete or update audit_events rows", () => {
15
17
  const repoRoot = path.resolve(__dirname, "../../../..");
@@ -56,8 +58,14 @@ describe("audit_events immutability", () => {
56
58
  "packages/core/src/pm/actions/resolve-approvals.ts",
57
59
  "packages/core/src/pm/actions/recover-stuck.ts",
58
60
  "packages/core/src/pipeline/review-providers-runner.ts",
59
- "packages/core/src/release-manager/scheduler.ts",
61
+ "packages/core/src/release-manager/release-tick.ts",
62
+ "packages/core/src/release-manager/release-helpers.ts",
60
63
  "packages/core/src/release-manager/slack-handler.ts",
64
+ // BEC-207: base-tier operational signal — see auth-monitor.ts comment
65
+ // for the rationale on bypassing the audit-log feature gate.
66
+ "packages/core/src/executor/auth-monitor.ts",
67
+ "packages/core/src/__tests__/auth-monitor.test.ts",
68
+ "packages/core/src/audit/events.ts",
61
69
  "packages/core/src/repo/agent-branch-sweep-runner.ts",
62
70
  "packages/core/src/qa/github.ts",
63
71
  "packages/core/src/qa/gap.ts",
@@ -76,5 +84,46 @@ describe("audit_events immutability", () => {
76
84
  .filter((file) => !allowed.some((a) => file.endsWith(a) || file === a));
77
85
  expect(offenders, `Unauthorized logAuditEventUnchecked usage in:\n${offenders.join("\n")}`).toEqual([]);
78
86
  });
87
+ /**
88
+ * Tier 1d — keep CLAUDE.md's claimed audit-event count in sync with
89
+ * AuditEventTypeSchema. The autonomous pipeline has historically added new
90
+ * event types to the schema without updating the doc count (see Tier 1a's
91
+ * pre-existing 17→41 drift). This test fails CI when the regex match
92
+ * `(\d+) event types` in CLAUDE.md disagrees with `AuditEventTypeSchema`'s
93
+ * length.
94
+ *
95
+ * The pipeline's review-stage prompt will surface this as a blocking
96
+ * `audit-count-drift` finding via the convention checklist (Tier 2);
97
+ * the unit test is the deterministic backstop.
98
+ */
99
+ it("CLAUDE.md audit-event count matches AuditEventTypeSchema length", () => {
100
+ const repoRoot = path.resolve(__dirname, "../../../..");
101
+ const claudeMd = readFileSync(path.join(repoRoot, "CLAUDE.md"), "utf8");
102
+ const matches = [...claudeMd.matchAll(/(\d+)\s+event\s+types/g)];
103
+ expect(matches.length, "CLAUDE.md must contain exactly one `<N> event types` sentence so Tier 1d can lock it down").toBe(1);
104
+ const documented = Number(matches[0][1]);
105
+ const actual = AuditEventTypeSchema.options.length;
106
+ expect(documented, `CLAUDE.md says "${documented} event types" but AuditEventTypeSchema has ${actual}. Update CLAUDE.md or the schema; they must match.`).toBe(actual);
107
+ });
108
+ /**
109
+ * Tier 1d — mirror of the event-type count check for the actor-type enum.
110
+ * The actor-type list is shorter and changes less often, but the same drift
111
+ * pattern applies (e.g. BEC-207 added a new actor without updating any doc).
112
+ * CLAUDE.md doesn't currently cite a number for actor types, so this test
113
+ * is gated: it only runs if a `(\d+) actor types` sentence is present, and
114
+ * fails when present-but-stale. Removes the test gracefully when the doc
115
+ * doesn't enumerate.
116
+ */
117
+ it("CLAUDE.md actor-type count matches AuditActorTypeSchema length (when present)", () => {
118
+ const repoRoot = path.resolve(__dirname, "../../../..");
119
+ const claudeMd = readFileSync(path.join(repoRoot, "CLAUDE.md"), "utf8");
120
+ const matches = [...claudeMd.matchAll(/(\d+)\s+actor\s+types/g)];
121
+ if (matches.length === 0)
122
+ return; // not enumerated; nothing to validate
123
+ expect(matches.length, "CLAUDE.md must contain at most one `<N> actor types` sentence").toBe(1);
124
+ const documented = Number(matches[0][1]);
125
+ const actual = AuditActorTypeSchema.options.length;
126
+ expect(documented, `CLAUDE.md says "${documented} actor types" but AuditActorTypeSchema has ${actual}.`).toBe(actual);
127
+ });
79
128
  });
80
129
  //# sourceMappingURL=audit-immutability.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"audit-immutability.test.js","sourceRoot":"","sources":["../../src/__tests__/audit-immutability.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit-immutability.test.js","sourceRoot":"","sources":["../../src/__tests__/audit-immutability.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEzE,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG;YACd,sCAAsC;YACtC,qDAAqD;YACrD,wDAAwD;SACzD,CAAC;QAEF,MAAM,QAAQ,GAAG;YACf,iCAAiC;YACjC,iCAAiC;SAClC,CAAC;QAEF,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,YAAY,CACtB,KAAK,EACL,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,kBAAkB,CAAC,EAC9C,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CACpC,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAC1D,CAAC;YAAC,MAAM,CAAC;gBACP,0DAA0D;YAC5D,CAAC;QACH,CAAC;QAED,MAAM,SAAS,GAAG,OAAO;aACtB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;aAClC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QAE1E,MAAM,CACJ,SAAS,EACT,2CAA2C,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,GAAG,EAAE;QAC3F,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACxD,wCAAwC;QACxC,sCAAsC;QACtC,oDAAoD;QACpD,iFAAiF;QACjF,4EAA4E;QAC5E,yEAAyE;QACzE,sBAAsB;QACtB,MAAM,OAAO,GAAG;YACd,mCAAmC;YACnC,8BAA8B;YAC9B,mCAAmC;YACnC,wCAAwC;YACxC,yCAAyC;YACzC,4CAA4C;YAC5C,mDAAmD;YACnD,+CAA+C;YAC/C,uDAAuD;YACvD,mDAAmD;YACnD,sDAAsD;YACtD,oDAAoD;YACpD,sEAAsE;YACtE,6DAA6D;YAC7D,4CAA4C;YAC5C,kDAAkD;YAClD,mCAAmC;YACnC,qDAAqD;YACrD,gCAAgC;YAChC,6BAA6B;YAC7B,wDAAwD;SACzD,CAAC;QAEF,IAAI,OAAO,GAAa,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CACtB,KAAK,EACL,CAAC,MAAM,EAAE,KAAK,EAAE,wBAAwB,EAAE,IAAI,EAAE,kBAAkB,CAAC,EACnE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CACpC,CAAC;YACF,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,aAAa;QACf,CAAC;QAED,MAAM,SAAS,GAAG,OAAO;aACtB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;aAClC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;QAE1E,MAAM,CACJ,SAAS,EACT,kDAAkD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACzE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH;;;;;;;;;;;OAWG;IACH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;QACzE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC,CAAC;QACjE,MAAM,CACJ,OAAO,CAAC,MAAM,EACd,2FAA2F,CAC5F,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACV,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC;QACnD,MAAM,CACJ,UAAU,EACV,mBAAmB,UAAU,8CAA8C,MAAM,oDAAoD,CACtI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IAEH;;;;;;;;OAQG;IACH,EAAE,CAAC,+EAA+E,EAAE,GAAG,EAAE;QACvF,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,sCAAsC;QACxE,MAAM,CACJ,OAAO,CAAC,MAAM,EACd,+DAA+D,CAChE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACV,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC;QACnD,MAAM,CACJ,UAAU,EACV,mBAAmB,UAAU,8CAA8C,MAAM,GAAG,CACrF,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth-monitor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-monitor.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/auth-monitor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Tests for AuthMonitor (BEC-207) — periodic Claude session health-check.
3
+ *
4
+ * Covers:
5
+ * - Returns early when CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY is set.
6
+ * - Runs `claude auth status` only when neither env var is set.
7
+ * - On expiry: sends Slack alert and writes claude.auth_expired audit event.
8
+ * - 6-hour throttle: skips check if interval has not elapsed.
9
+ * - createAuthMonitor stateful wrapper manages lastCheckTime across calls.
10
+ */
11
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
12
+ // ---------------------------------------------------------------------------
13
+ // Mocks — must be declared before any imports that use the mocked modules.
14
+ // vi.mock() is hoisted by vitest, so these run before module initialisation.
15
+ // ---------------------------------------------------------------------------
16
+ // Mock child_process.execFile used by runAuthMonitorCheck
17
+ vi.mock("node:child_process", () => ({
18
+ execFile: vi.fn(),
19
+ }));
20
+ // Mock postSlackMessage
21
+ vi.mock("../pm/slack-helpers.js", () => ({
22
+ postSlackMessage: vi.fn().mockResolvedValue({ ok: true }),
23
+ }));
24
+ // Mock audit writer
25
+ vi.mock("../audit/writer.js", () => ({
26
+ logAuditEventUnchecked: vi.fn().mockResolvedValue(undefined),
27
+ }));
28
+ // Mock resetAuthCheckCache (auth-monitor calls this to bypass the 5-min cache)
29
+ vi.mock("../executor/auth-check.js", () => ({
30
+ resetAuthCheckCache: vi.fn(),
31
+ resolveClaudeAuth: vi.fn(() => ({ method: "session" })),
32
+ isClaudeAuthValid: vi.fn().mockResolvedValue(true),
33
+ }));
34
+ // ---------------------------------------------------------------------------
35
+ // Static imports (mocks are in place due to hoisting)
36
+ // ---------------------------------------------------------------------------
37
+ import { execFile } from "node:child_process";
38
+ import { postSlackMessage } from "../pm/slack-helpers.js";
39
+ import { logAuditEventUnchecked } from "../audit/writer.js";
40
+ import { runAuthMonitorCheck, createAuthMonitor } from "../executor/auth-monitor.js";
41
+ const mockExecFile = vi.mocked(execFile);
42
+ const mockPostSlackMessage = vi.mocked(postSlackMessage);
43
+ const mockLogAuditEvent = vi.mocked(logAuditEventUnchecked);
44
+ // ---------------------------------------------------------------------------
45
+ // Helpers
46
+ // ---------------------------------------------------------------------------
47
+ function simulateCliSuccess() {
48
+ mockExecFile.mockImplementation((_cmd, _args, _opts, cb) => {
49
+ cb(null);
50
+ return {};
51
+ });
52
+ }
53
+ function simulateCliFailure() {
54
+ mockExecFile.mockImplementation((_cmd, _args, _opts, cb) => {
55
+ cb(new Error("session expired"));
56
+ return {};
57
+ });
58
+ }
59
+ const SMALL_INTERVAL = 100; // 100 ms — always elapsed unless we use a very recent timestamp
60
+ // ---------------------------------------------------------------------------
61
+ // Tests: runAuthMonitorCheck
62
+ // ---------------------------------------------------------------------------
63
+ describe("runAuthMonitorCheck", () => {
64
+ let savedOauthToken;
65
+ let savedApiKey;
66
+ beforeEach(() => {
67
+ mockExecFile.mockReset();
68
+ mockPostSlackMessage.mockReset().mockResolvedValue({ ok: true });
69
+ mockLogAuditEvent.mockReset().mockResolvedValue(undefined);
70
+ // Save and clear env vars so each test starts with a clean slate
71
+ savedOauthToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
72
+ savedApiKey = process.env.ANTHROPIC_API_KEY;
73
+ delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
74
+ delete process.env.ANTHROPIC_API_KEY;
75
+ });
76
+ afterEach(() => {
77
+ if (savedOauthToken === undefined) {
78
+ delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
79
+ }
80
+ else {
81
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = savedOauthToken;
82
+ }
83
+ if (savedApiKey === undefined) {
84
+ delete process.env.ANTHROPIC_API_KEY;
85
+ }
86
+ else {
87
+ process.env.ANTHROPIC_API_KEY = savedApiKey;
88
+ }
89
+ });
90
+ // --- Throttle ---
91
+ it("skips check when interval has not elapsed (lastCheckTime is recent)", async () => {
92
+ const recentTime = Date.now(); // just ran
93
+ await runAuthMonitorCheck(recentTime, {}, 60_000); // 60-second interval
94
+ // Should not run execFile because interval hasn't elapsed
95
+ expect(mockExecFile).not.toHaveBeenCalled();
96
+ });
97
+ it("runs check when interval has elapsed (lastCheckTime = 0 = never)", async () => {
98
+ simulateCliSuccess();
99
+ await runAuthMonitorCheck(0, {}, SMALL_INTERVAL);
100
+ expect(mockExecFile).toHaveBeenCalledTimes(1);
101
+ });
102
+ // --- Env-var short-circuit (AC #8) ---
103
+ it("returns early without subprocess when CLAUDE_CODE_OAUTH_TOKEN is set", async () => {
104
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = "sk-ant-oat-test";
105
+ await runAuthMonitorCheck(0, {}, SMALL_INTERVAL);
106
+ expect(mockExecFile).not.toHaveBeenCalled();
107
+ expect(mockPostSlackMessage).not.toHaveBeenCalled();
108
+ expect(mockLogAuditEvent).not.toHaveBeenCalled();
109
+ });
110
+ it("returns early without subprocess when ANTHROPIC_API_KEY is set", async () => {
111
+ process.env.ANTHROPIC_API_KEY = "sk-ant-api03-test";
112
+ await runAuthMonitorCheck(0, {}, SMALL_INTERVAL);
113
+ expect(mockExecFile).not.toHaveBeenCalled();
114
+ expect(mockPostSlackMessage).not.toHaveBeenCalled();
115
+ expect(mockLogAuditEvent).not.toHaveBeenCalled();
116
+ });
117
+ it("returns early without subprocess when both env vars are set", async () => {
118
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = "sk-ant-oat-test";
119
+ process.env.ANTHROPIC_API_KEY = "sk-ant-api03-test";
120
+ await runAuthMonitorCheck(0, {}, SMALL_INTERVAL);
121
+ expect(mockExecFile).not.toHaveBeenCalled();
122
+ });
123
+ // --- Session valid ---
124
+ it("runs subprocess and skips alerts when session is valid", async () => {
125
+ simulateCliSuccess();
126
+ await runAuthMonitorCheck(0, {
127
+ slackBotToken: "xoxb-test",
128
+ slackErrorChannel: "CTEST",
129
+ }, SMALL_INTERVAL);
130
+ expect(mockExecFile).toHaveBeenCalledTimes(1);
131
+ expect(mockPostSlackMessage).not.toHaveBeenCalled();
132
+ expect(mockLogAuditEvent).not.toHaveBeenCalled();
133
+ });
134
+ // --- Session expired (AC #7, #15) ---
135
+ it("sends Slack alert to configured channel when session is expired", async () => {
136
+ simulateCliFailure();
137
+ await runAuthMonitorCheck(0, {
138
+ slackBotToken: "xoxb-bot-token",
139
+ slackErrorChannel: "CERROR",
140
+ }, SMALL_INTERVAL);
141
+ expect(mockPostSlackMessage).toHaveBeenCalledTimes(1);
142
+ expect(mockPostSlackMessage).toHaveBeenCalledWith("xoxb-bot-token", expect.objectContaining({ channel: "CERROR" }));
143
+ });
144
+ it("logs claude.auth_expired audit event when session is expired and db provided (AC #7)", async () => {
145
+ simulateCliFailure();
146
+ const fakeDb = {};
147
+ await runAuthMonitorCheck(0, { db: fakeDb }, SMALL_INTERVAL);
148
+ expect(mockLogAuditEvent).toHaveBeenCalledTimes(1);
149
+ const [calledDb, calledEvent] = mockLogAuditEvent.mock.calls[0];
150
+ expect(calledDb).toBe(fakeDb);
151
+ expect(calledEvent.eventType).toBe("claude.auth_expired");
152
+ expect(calledEvent.actor).toBe("system");
153
+ expect(calledEvent.payload).toMatchObject({
154
+ detectedAt: expect.any(String),
155
+ });
156
+ });
157
+ it("does not throw when Slack post fails", async () => {
158
+ simulateCliFailure();
159
+ mockPostSlackMessage.mockRejectedValue(new Error("network error"));
160
+ await expect(runAuthMonitorCheck(0, {
161
+ slackBotToken: "xoxb-test",
162
+ slackErrorChannel: "CTEST",
163
+ }, SMALL_INTERVAL)).resolves.not.toThrow();
164
+ });
165
+ it("skips Slack when no slackBotToken configured", async () => {
166
+ simulateCliFailure();
167
+ await runAuthMonitorCheck(0, { slackErrorChannel: "CTEST" }, SMALL_INTERVAL);
168
+ expect(mockPostSlackMessage).not.toHaveBeenCalled();
169
+ });
170
+ it("skips Slack when no slackErrorChannel configured", async () => {
171
+ simulateCliFailure();
172
+ await runAuthMonitorCheck(0, { slackBotToken: "xoxb-test" }, SMALL_INTERVAL);
173
+ expect(mockPostSlackMessage).not.toHaveBeenCalled();
174
+ });
175
+ // --- Return value (lastCheckTime update) ---
176
+ it("returns updated timestamp after running", async () => {
177
+ simulateCliSuccess();
178
+ const before = Date.now();
179
+ const returned = await runAuthMonitorCheck(0, {}, SMALL_INTERVAL);
180
+ const after = Date.now();
181
+ expect(returned).toBeGreaterThanOrEqual(before);
182
+ expect(returned).toBeLessThanOrEqual(after);
183
+ });
184
+ it("returns original lastCheckTime when interval has not elapsed", async () => {
185
+ const recentTime = Date.now();
186
+ const returned = await runAuthMonitorCheck(recentTime, {}, 60_000);
187
+ expect(returned).toBe(recentTime);
188
+ });
189
+ });
190
+ // ---------------------------------------------------------------------------
191
+ // Tests: createAuthMonitor stateful wrapper (AC #6)
192
+ // ---------------------------------------------------------------------------
193
+ describe("createAuthMonitor", () => {
194
+ let savedOauthToken;
195
+ let savedApiKey;
196
+ beforeEach(() => {
197
+ mockExecFile.mockReset();
198
+ mockPostSlackMessage.mockReset().mockResolvedValue({ ok: true });
199
+ mockLogAuditEvent.mockReset().mockResolvedValue(undefined);
200
+ savedOauthToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
201
+ savedApiKey = process.env.ANTHROPIC_API_KEY;
202
+ delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
203
+ delete process.env.ANTHROPIC_API_KEY;
204
+ });
205
+ afterEach(() => {
206
+ if (savedOauthToken === undefined) {
207
+ delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
208
+ }
209
+ else {
210
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = savedOauthToken;
211
+ }
212
+ if (savedApiKey === undefined) {
213
+ delete process.env.ANTHROPIC_API_KEY;
214
+ }
215
+ else {
216
+ process.env.ANTHROPIC_API_KEY = savedApiKey;
217
+ }
218
+ });
219
+ it("tick() runs the check and handles errors gracefully", async () => {
220
+ simulateCliSuccess();
221
+ const monitor = createAuthMonitor({}, SMALL_INTERVAL);
222
+ await expect(monitor.tick()).resolves.not.toThrow();
223
+ expect(mockExecFile).toHaveBeenCalledTimes(1);
224
+ });
225
+ it("tick() is throttled — second call immediately after first is a no-op", async () => {
226
+ simulateCliSuccess();
227
+ const monitor = createAuthMonitor({}, 60_000); // 60s interval
228
+ await monitor.tick(); // first tick — runs
229
+ await monitor.tick(); // second tick immediately — throttled
230
+ expect(mockExecFile).toHaveBeenCalledTimes(1); // only one subprocess
231
+ });
232
+ it("tick() skips session check when CLAUDE_CODE_OAUTH_TOKEN is set (AC #8)", async () => {
233
+ process.env.CLAUDE_CODE_OAUTH_TOKEN = "sk-ant-oat-test";
234
+ const monitor = createAuthMonitor({}, SMALL_INTERVAL);
235
+ await monitor.tick();
236
+ expect(mockExecFile).not.toHaveBeenCalled();
237
+ });
238
+ it("tick() sends alert on expired session and logs audit event (AC #6, #7)", async () => {
239
+ simulateCliFailure();
240
+ const fakeDb = {};
241
+ const monitor = createAuthMonitor({
242
+ slackBotToken: "xoxb-test",
243
+ slackErrorChannel: "CALERTS",
244
+ db: fakeDb,
245
+ }, SMALL_INTERVAL);
246
+ await monitor.tick();
247
+ expect(mockPostSlackMessage).toHaveBeenCalledWith("xoxb-test", expect.objectContaining({ channel: "CALERTS" }));
248
+ expect(mockLogAuditEvent).toHaveBeenCalledTimes(1);
249
+ const [, calledEvent] = mockLogAuditEvent.mock.calls[0];
250
+ expect(calledEvent.eventType).toBe("claude.auth_expired");
251
+ });
252
+ });
253
+ //# sourceMappingURL=auth-monitor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-monitor.test.js","sourceRoot":"","sources":["../../src/__tests__/auth-monitor.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * BEC-186: pull_request.closed + merged=true must transition Linear to Done.
3
+ *
4
+ * When a human (or GitHub's "auto-merge when ready") merges a PR after the
5
+ * pipeline has already completed, the pipeline's `onPipelineComplete` has
6
+ * already fired and will not re-fire. Without an explicit handler for
7
+ * `pull_request.closed+merged`, the Linear ticket stays "In Review" forever.
8
+ *
9
+ * Fix implemented in `webhook/github-handler.ts`:
10
+ * - A new routing branch catches `pull_request.closed` + `merged: true`
11
+ * - Looks up the pipeline run by pr_url
12
+ * - Marks `auto_merged = true` in the DB
13
+ * - Calls `notifier.onPRMerged?.(run)` → LinearNotifier transitions to Done
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=bec-186-repro.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bec-186-repro.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bec-186-repro.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,223 @@
1
+ /**
2
+ * BEC-186: pull_request.closed + merged=true must transition Linear to Done.
3
+ *
4
+ * When a human (or GitHub's "auto-merge when ready") merges a PR after the
5
+ * pipeline has already completed, the pipeline's `onPipelineComplete` has
6
+ * already fired and will not re-fire. Without an explicit handler for
7
+ * `pull_request.closed+merged`, the Linear ticket stays "In Review" forever.
8
+ *
9
+ * Fix implemented in `webhook/github-handler.ts`:
10
+ * - A new routing branch catches `pull_request.closed` + `merged: true`
11
+ * - Looks up the pipeline run by pr_url
12
+ * - Marks `auto_merged = true` in the DB
13
+ * - Calls `notifier.onPRMerged?.(run)` → LinearNotifier transitions to Done
14
+ */
15
+ import { describe, it, expect, vi, beforeEach } from "vitest";
16
+ import { createHmac } from "crypto";
17
+ import { createGitHubWebhookHandler } from "../webhook/github-handler.js";
18
+ // ---------------------------------------------------------------------------
19
+ // Helpers (mirrors existing test infrastructure)
20
+ // ---------------------------------------------------------------------------
21
+ const SECRET = "gh-webhook-secret";
22
+ function sign(body, secret) {
23
+ return `sha256=${createHmac("sha256", secret).update(body).digest("hex")}`;
24
+ }
25
+ const pipelineConfig = {
26
+ name: "auto-implement",
27
+ stages: ["triage", "implement", "test", "review"],
28
+ retry: { maxAttempts: 1, strategy: "fix-and-retry" },
29
+ review: { requiredApprovals: 1 },
30
+ prStrategy: "draft",
31
+ };
32
+ const repoConfig = {
33
+ url: "https://github.com/org/repo",
34
+ defaultBranch: "main",
35
+ testCommand: "npm test",
36
+ buildCommand: "npm run build",
37
+ githubFeedback: {
38
+ allowedReviewers: [],
39
+ botLogins: ["linear-agent[bot]"],
40
+ autoTrigger: true,
41
+ },
42
+ };
43
+ /** Simulated DB row representing a completed pipeline run whose PR was opened. */
44
+ const mockRun = {
45
+ id: "run-bec186",
46
+ issueId: "BEC-179",
47
+ issueTitle: "Worktree detached HEAD bug",
48
+ pipelineKey: "auto-implement",
49
+ repoUrl: "https://github.com/org/repo",
50
+ branch: "agent/BEC-179-worktree-detached-head",
51
+ status: "completed",
52
+ prUrl: "https://github.com/org/repo/pull/235",
53
+ autoMerged: null,
54
+ runType: "standard",
55
+ parentRunId: null,
56
+ feedbackContext: null,
57
+ };
58
+ function makeMockDb(run = mockRun) {
59
+ const updateMock = vi.fn().mockReturnValue({
60
+ set: vi.fn().mockReturnValue({
61
+ where: vi.fn().mockResolvedValue(undefined),
62
+ }),
63
+ });
64
+ return {
65
+ select: vi.fn().mockReturnValue({
66
+ from: vi.fn().mockReturnValue({
67
+ where: vi.fn().mockReturnValue({
68
+ limit: vi.fn().mockResolvedValue(run ? [run] : []),
69
+ }),
70
+ }),
71
+ }),
72
+ update: updateMock,
73
+ _updateMock: updateMock,
74
+ };
75
+ }
76
+ function makeMockRunner() {
77
+ return {
78
+ startFeedback: vi.fn().mockResolvedValue(undefined),
79
+ isActiveFeedback: vi.fn().mockReturnValue(false),
80
+ };
81
+ }
82
+ function makeMockNotifier() {
83
+ return {
84
+ onPipelineStart: vi.fn().mockResolvedValue(undefined),
85
+ onStageComplete: vi.fn().mockResolvedValue(undefined),
86
+ onPipelineComplete: vi.fn().mockResolvedValue(undefined),
87
+ onPipelineFailed: vi.fn().mockResolvedValue(undefined),
88
+ onPRMerged: vi.fn().mockResolvedValue(undefined),
89
+ };
90
+ }
91
+ function buildConfig(overrides = {}) {
92
+ return {
93
+ webhookSecret: SECRET,
94
+ runner: makeMockRunner(),
95
+ pipelineConfigs: { "auto-implement": pipelineConfig },
96
+ repoConfigs: { "org/repo": repoConfig },
97
+ db: makeMockDb(),
98
+ ...overrides,
99
+ };
100
+ }
101
+ /** Creates a synthetic pull_request.closed+merged webhook payload. */
102
+ function makePRClosedMergedPayload(overrides = {}) {
103
+ return {
104
+ action: "closed",
105
+ pull_request: {
106
+ number: 235,
107
+ html_url: "https://github.com/org/repo/pull/235",
108
+ merged: true,
109
+ head: { ref: "agent/BEC-179-worktree-detached-head" },
110
+ merge_commit_sha: "abc123def456",
111
+ },
112
+ repository: {
113
+ name: "repo",
114
+ owner: { login: "org" },
115
+ html_url: "https://github.com/org/repo",
116
+ },
117
+ ...overrides,
118
+ };
119
+ }
120
+ async function postWebhook(app, body, event, secret = SECRET) {
121
+ const rawBody = JSON.stringify(body);
122
+ const sig = sign(rawBody, secret);
123
+ return app.request("/webhooks/github", {
124
+ method: "POST",
125
+ headers: {
126
+ "Content-Type": "application/json",
127
+ "X-GitHub-Event": event,
128
+ "X-Hub-Signature-256": sig,
129
+ },
130
+ body: rawBody,
131
+ });
132
+ }
133
+ // ---------------------------------------------------------------------------
134
+ // BEC-186 tests — verifying the fix
135
+ // ---------------------------------------------------------------------------
136
+ describe("BEC-186: pull_request.closed + merged=true → Linear Done transition", () => {
137
+ let runner;
138
+ beforeEach(() => {
139
+ vi.restoreAllMocks();
140
+ runner = makeMockRunner();
141
+ });
142
+ it("pull_request.closed+merged=true is handled (not skipped)", async () => {
143
+ // The core fix: action="closed"+merged=true must NOT fall through to
144
+ // the "unhandled event type" branch.
145
+ const db = makeMockDb(mockRun);
146
+ const app = createGitHubWebhookHandler(buildConfig({ runner: runner, db: db }));
147
+ const res = await postWebhook(app, makePRClosedMergedPayload(), "pull_request");
148
+ expect(res.status).toBe(200);
149
+ const json = await res.json();
150
+ expect(json).not.toHaveProperty("skipped");
151
+ expect(json.ok).toBe(true);
152
+ expect(json.action).toBe("pr-merged");
153
+ });
154
+ it("DB is updated with auto_merged=true on PR merge", async () => {
155
+ const db = makeMockDb(mockRun);
156
+ const app = createGitHubWebhookHandler(buildConfig({ runner: runner, db: db }));
157
+ await postWebhook(app, makePRClosedMergedPayload(), "pull_request");
158
+ expect(db._updateMock).toHaveBeenCalled();
159
+ });
160
+ it("notifier.onPRMerged is called when notifier is provided", async () => {
161
+ const db = makeMockDb(mockRun);
162
+ const notifier = makeMockNotifier();
163
+ const app = createGitHubWebhookHandler(buildConfig({ runner: runner, db: db, notifier: notifier }));
164
+ await postWebhook(app, makePRClosedMergedPayload(), "pull_request");
165
+ expect(notifier.onPRMerged).toHaveBeenCalledOnce();
166
+ expect(notifier.onPRMerged).toHaveBeenCalledWith(expect.objectContaining({ id: "run-bec186" }));
167
+ });
168
+ it("notifier.onPRMerged is NOT called when no pipeline run is found", async () => {
169
+ // PR from a non-agent source — no DB row matches
170
+ const db = makeMockDb(null);
171
+ const notifier = makeMockNotifier();
172
+ const app = createGitHubWebhookHandler(buildConfig({ runner: runner, db: db, notifier: notifier }));
173
+ const res = await postWebhook(app, makePRClosedMergedPayload(), "pull_request");
174
+ expect(res.status).toBe(200);
175
+ const json = await res.json();
176
+ // Still returns ok: true, action: "pr-merged" (handler ran, just found no run)
177
+ expect(json.ok).toBe(true);
178
+ expect(notifier.onPRMerged).not.toHaveBeenCalled();
179
+ });
180
+ it("idempotency: already-merged run is not re-processed", async () => {
181
+ // If auto_merged is already true, skip the update and notifier call
182
+ const alreadyMergedRun = { ...mockRun, autoMerged: true };
183
+ const db = makeMockDb(alreadyMergedRun);
184
+ const notifier = makeMockNotifier();
185
+ const app = createGitHubWebhookHandler(buildConfig({ runner: runner, db: db, notifier: notifier }));
186
+ await postWebhook(app, makePRClosedMergedPayload(), "pull_request");
187
+ expect(db._updateMock).not.toHaveBeenCalled();
188
+ expect(notifier.onPRMerged).not.toHaveBeenCalled();
189
+ });
190
+ it("pull_request.closed WITHOUT merged=true is skipped (no Done transition)", async () => {
191
+ // PR closed without merge (e.g. closed manually) must NOT trigger Done transition.
192
+ const db = makeMockDb(mockRun);
193
+ const notifier = makeMockNotifier();
194
+ const payload = {
195
+ ...makePRClosedMergedPayload(),
196
+ pull_request: {
197
+ ...makePRClosedMergedPayload().pull_request,
198
+ merged: false,
199
+ },
200
+ };
201
+ const app = createGitHubWebhookHandler(buildConfig({ runner: runner, db: db, notifier: notifier }));
202
+ const res = await postWebhook(app, payload, "pull_request");
203
+ expect(res.status).toBe(200);
204
+ const json = await res.json();
205
+ // Falls through to "unhandled event type" — correct outcome
206
+ expect(json.skipped).toBeDefined();
207
+ expect(db._updateMock).not.toHaveBeenCalled();
208
+ expect(notifier.onPRMerged).not.toHaveBeenCalled();
209
+ });
210
+ it("works when notifier is not configured (no crash)", async () => {
211
+ // Notifier is optional — handler must not throw when absent
212
+ const db = makeMockDb(mockRun);
213
+ const app = createGitHubWebhookHandler(buildConfig({ runner: runner, db: db /* no notifier */ }));
214
+ const res = await postWebhook(app, makePRClosedMergedPayload(), "pull_request");
215
+ expect(res.status).toBe(200);
216
+ const json = await res.json();
217
+ expect(json.ok).toBe(true);
218
+ expect(json.action).toBe("pr-merged");
219
+ // DB still updated even without notifier
220
+ expect(db._updateMock).toHaveBeenCalled();
221
+ });
222
+ });
223
+ //# sourceMappingURL=bec-186-repro.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bec-186-repro.test.js","sourceRoot":"","sources":["../../src/__tests__/bec-186-repro.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=db-migrations.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-migrations.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/db-migrations.test.ts"],"names":[],"mappings":""}