From 2bddf055e524c4ab0a4660f95260ad13b67007c4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 05:11:41 +0000
Subject: [PATCH 1/2] Initial plan
From bc557ee224075a1113b47d4868cf9244e4e00e3b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 18 Feb 2026 05:19:45 +0000
Subject: [PATCH 2/2] Extract shared expired entity handler to eliminate
duplicate code
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.../setup/js/close_expired_discussions.cjs | 77 ++---
actions/setup/js/close_expired_issues.cjs | 34 +--
.../setup/js/close_expired_pull_requests.cjs | 34 +--
actions/setup/js/expired_entity_handlers.cjs | 102 +++++++
.../setup/js/expired_entity_handlers.test.cjs | 274 ++++++++++++++++++
5 files changed, 439 insertions(+), 82 deletions(-)
create mode 100644 actions/setup/js/expired_entity_handlers.cjs
create mode 100644 actions/setup/js/expired_entity_handlers.test.cjs
diff --git a/actions/setup/js/close_expired_discussions.cjs b/actions/setup/js/close_expired_discussions.cjs
index 0b78b9dbf1..63e31624dd 100644
--- a/actions/setup/js/close_expired_discussions.cjs
+++ b/actions/setup/js/close_expired_discussions.cjs
@@ -5,6 +5,7 @@ const { executeExpiredEntityCleanup } = require("./expired_entity_main_flow.cjs"
const { generateExpiredEntityFooter } = require("./generate_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { getWorkflowMetadata } = require("./workflow_metadata_helpers.cjs");
+const { createExpiredEntityProcessor } = require("./expired_entity_handlers.cjs");
/**
* Add comment to a GitHub Discussion using GraphQL
@@ -96,67 +97,51 @@ async function main() {
// Get workflow metadata for footer
const { workflowName, workflowId, runUrl } = getWorkflowMetadata(owner, repo);
- await executeExpiredEntityCleanup(github, owner, repo, {
- entityType: "discussions",
- graphqlField: "discussions",
- resultKey: "discussions",
- entityLabel: "Discussion",
- summaryHeading: "Expired Discussions Cleanup",
- enableDedupe: true, // Discussions may have duplicates across pages
- includeSkippedHeading: true,
- processEntity: async discussion => {
+ // Create processor using shared handler with discussion-specific pre-check
+ const processEntity = createExpiredEntityProcessor(workflowName, runUrl, workflowId, {
+ entityType: "discussion",
+ addComment: async (discussion, message) => {
+ await addDiscussionComment(github, discussion.id, message);
+ },
+ closeEntity: async discussion => {
+ await closeDiscussionAsOutdated(github, discussion.id);
+ },
+ buildClosingMessage: (discussion, workflowName, runUrl, workflowId) => {
+ return `This discussion was automatically closed because it expired on ${discussion.expirationDate.toISOString()}.` + generateExpiredEntityFooter(workflowName, runUrl, workflowId) + "\n\n";
+ },
+ preCheck: async discussion => {
core.info(` Checking for existing expiration comment and closed state on discussion #${discussion.number}`);
const { hasComment, isClosed } = await hasExpirationComment(github, discussion.id);
if (isClosed) {
- core.warning(` Discussion #${discussion.number} is already closed, skipping`);
return {
- status: "skipped",
- record: {
- number: discussion.number,
- url: discussion.url,
- title: discussion.title,
- },
+ shouldSkip: true,
+ reason: `Discussion #${discussion.number} is already closed, skipping`,
};
}
if (hasComment) {
- core.warning(` Discussion #${discussion.number} already has an expiration comment, skipping to avoid duplicate`);
-
- core.info(` Attempting to close discussion #${discussion.number} without adding another comment`);
- await closeDiscussionAsOutdated(github, discussion.id);
- core.info(` ✓ Discussion closed successfully`);
-
return {
- status: "skipped",
- record: {
- number: discussion.number,
- url: discussion.url,
- title: discussion.title,
- },
+ shouldSkip: true,
+ shouldClose: true,
+ reason: `Discussion #${discussion.number} already has an expiration comment, skipping to avoid duplicate`,
};
}
- const closingMessage = `This discussion was automatically closed because it expired on ${discussion.expirationDate.toISOString()}.` + generateExpiredEntityFooter(workflowName, runUrl, workflowId) + "\n\n";
-
- core.info(` Adding closing comment to discussion #${discussion.number}`);
- await addDiscussionComment(github, discussion.id, closingMessage);
- core.info(` ✓ Comment added successfully`);
-
- core.info(` Closing discussion #${discussion.number} as outdated`);
- await closeDiscussionAsOutdated(github, discussion.id);
- core.info(` ✓ Discussion closed successfully`);
-
- return {
- status: "closed",
- record: {
- number: discussion.number,
- url: discussion.url,
- title: discussion.title,
- },
- };
+ return { shouldSkip: false };
},
});
+
+ await executeExpiredEntityCleanup(github, owner, repo, {
+ entityType: "discussions",
+ graphqlField: "discussions",
+ resultKey: "discussions",
+ entityLabel: "Discussion",
+ summaryHeading: "Expired Discussions Cleanup",
+ enableDedupe: true, // Discussions may have duplicates across pages
+ includeSkippedHeading: true,
+ processEntity,
+ });
}
module.exports = { main };
diff --git a/actions/setup/js/close_expired_issues.cjs b/actions/setup/js/close_expired_issues.cjs
index 454168ffaa..529090c091 100644
--- a/actions/setup/js/close_expired_issues.cjs
+++ b/actions/setup/js/close_expired_issues.cjs
@@ -5,6 +5,7 @@ const { executeExpiredEntityCleanup } = require("./expired_entity_main_flow.cjs"
const { generateExpiredEntityFooter } = require("./generate_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { getWorkflowMetadata } = require("./workflow_metadata_helpers.cjs");
+const { createExpiredEntityProcessor } = require("./expired_entity_handlers.cjs");
/**
* Add comment to a GitHub Issue using REST API
@@ -53,30 +54,27 @@ async function main() {
// Get workflow metadata for footer
const { workflowName, workflowId, runUrl } = getWorkflowMetadata(owner, repo);
+ // Create processor using shared handler
+ const processEntity = createExpiredEntityProcessor(workflowName, runUrl, workflowId, {
+ entityType: "issue",
+ addComment: async (issue, message) => {
+ await addIssueComment(github, owner, repo, issue.number, message);
+ },
+ closeEntity: async issue => {
+ await closeIssue(github, owner, repo, issue.number);
+ },
+ buildClosingMessage: (issue, workflowName, runUrl, workflowId) => {
+ return `This issue was automatically closed because it expired on ${issue.expirationDate.toISOString()}.` + generateExpiredEntityFooter(workflowName, runUrl, workflowId);
+ },
+ });
+
await executeExpiredEntityCleanup(github, owner, repo, {
entityType: "issues",
graphqlField: "issues",
resultKey: "issues",
entityLabel: "Issue",
summaryHeading: "Expired Issues Cleanup",
- processEntity: async issue => {
- const closingMessage = `This issue was automatically closed because it expired on ${issue.expirationDate.toISOString()}.` + generateExpiredEntityFooter(workflowName, runUrl, workflowId);
-
- await addIssueComment(github, owner, repo, issue.number, closingMessage);
- core.info(` ✓ Comment added successfully`);
-
- await closeIssue(github, owner, repo, issue.number);
- core.info(` ✓ Issue closed successfully`);
-
- return {
- status: "closed",
- record: {
- number: issue.number,
- url: issue.url,
- title: issue.title,
- },
- };
- },
+ processEntity,
});
}
diff --git a/actions/setup/js/close_expired_pull_requests.cjs b/actions/setup/js/close_expired_pull_requests.cjs
index db4559fd2b..6958e66c7c 100644
--- a/actions/setup/js/close_expired_pull_requests.cjs
+++ b/actions/setup/js/close_expired_pull_requests.cjs
@@ -5,6 +5,7 @@ const { executeExpiredEntityCleanup } = require("./expired_entity_main_flow.cjs"
const { generateExpiredEntityFooter } = require("./generate_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { getWorkflowMetadata } = require("./workflow_metadata_helpers.cjs");
+const { createExpiredEntityProcessor } = require("./expired_entity_handlers.cjs");
/**
* Add comment to a GitHub Pull Request using REST API
@@ -52,30 +53,27 @@ async function main() {
// Get workflow metadata for footer
const { workflowName, workflowId, runUrl } = getWorkflowMetadata(owner, repo);
+ // Create processor using shared handler
+ const processEntity = createExpiredEntityProcessor(workflowName, runUrl, workflowId, {
+ entityType: "pull request",
+ addComment: async (pr, message) => {
+ await addPullRequestComment(github, owner, repo, pr.number, message);
+ },
+ closeEntity: async pr => {
+ await closePullRequest(github, owner, repo, pr.number);
+ },
+ buildClosingMessage: (pr, workflowName, runUrl, workflowId) => {
+ return `This pull request was automatically closed because it expired on ${pr.expirationDate.toISOString()}.` + generateExpiredEntityFooter(workflowName, runUrl, workflowId);
+ },
+ });
+
await executeExpiredEntityCleanup(github, owner, repo, {
entityType: "pull requests",
graphqlField: "pullRequests",
resultKey: "pullRequests",
entityLabel: "Pull Request",
summaryHeading: "Expired Pull Requests Cleanup",
- processEntity: async pr => {
- const closingMessage = `This pull request was automatically closed because it expired on ${pr.expirationDate.toISOString()}.` + generateExpiredEntityFooter(workflowName, runUrl, workflowId);
-
- await addPullRequestComment(github, owner, repo, pr.number, closingMessage);
- core.info(` ✓ Comment added successfully`);
-
- await closePullRequest(github, owner, repo, pr.number);
- core.info(` ✓ Pull request closed successfully`);
-
- return {
- status: "closed",
- record: {
- number: pr.number,
- url: pr.url,
- title: pr.title,
- },
- };
- },
+ processEntity,
});
}
diff --git a/actions/setup/js/expired_entity_handlers.cjs b/actions/setup/js/expired_entity_handlers.cjs
new file mode 100644
index 0000000000..a7fa5775da
--- /dev/null
+++ b/actions/setup/js/expired_entity_handlers.cjs
@@ -0,0 +1,102 @@
+// @ts-check
+//
+
+/**
+ * Expired Entity Handlers
+ *
+ * This module provides reusable handlers for processing expired entities (issues, PRs, discussions).
+ * It extracts the common comment + close + return record flow that was duplicated across
+ * close_expired_issues.cjs, close_expired_pull_requests.cjs, and close_expired_discussions.cjs.
+ */
+
+/**
+ * Configuration for entity-specific operations
+ * @typedef {Object} EntityHandlerConfig
+ * @property {string} entityType - Entity type for logging (e.g., "issue", "pull request", "discussion")
+ * @property {(entity: any, message: string) => Promise} addComment - Function to add a comment (receives entity and message)
+ * @property {(entity: any) => Promise} closeEntity - Function to close the entity (receives entity)
+ * @property {(entity: any, workflowName: string, runUrl: string, workflowId: string) => string} buildClosingMessage - Function to build the closing message
+ * @property {(entity: any) => Promise<{shouldSkip: boolean, reason?: string, shouldClose?: boolean}>} [preCheck] - Optional pre-check function (e.g., for duplicate detection)
+ */
+
+/**
+ * Create a standard expired entity processor
+ *
+ * This function returns a processEntity function that can be passed to executeExpiredEntityCleanup.
+ * It handles the common flow:
+ * 1. Optional pre-check (e.g., checking for existing comments in discussions)
+ * 2. Add closing comment
+ * 3. Close entity
+ * 4. Return status and record
+ *
+ * @param {string} workflowName - Workflow name for footer
+ * @param {string} runUrl - Workflow run URL for footer
+ * @param {string} workflowId - Workflow ID for footer
+ * @param {EntityHandlerConfig} config - Entity-specific configuration
+ * @returns {(entity: any) => Promise<{status: "closed" | "skipped", record: any}>}
+ */
+function createExpiredEntityProcessor(workflowName, runUrl, workflowId, config) {
+ return async entity => {
+ // Step 1: Optional pre-check (e.g., duplicate detection for discussions)
+ if (config.preCheck) {
+ const preCheckResult = await config.preCheck(entity);
+ if (preCheckResult.shouldSkip) {
+ if (preCheckResult.reason) {
+ core.warning(` ${preCheckResult.reason}`);
+ }
+
+ // If preCheck says to close without adding comment, do that
+ if (preCheckResult.shouldClose) {
+ core.info(` Attempting to close ${config.entityType} #${entity.number} without adding another comment`);
+ await config.closeEntity(entity);
+ core.info(` ✓ ${capitalize(config.entityType)} closed successfully`);
+ }
+
+ return {
+ status: "skipped",
+ record: {
+ number: entity.number,
+ url: entity.url,
+ title: entity.title,
+ },
+ };
+ }
+ }
+
+ // Step 2: Build closing message
+ const closingMessage = config.buildClosingMessage(entity, workflowName, runUrl, workflowId);
+
+ // Step 3: Add closing comment
+ core.info(` Adding closing comment to ${config.entityType} #${entity.number}`);
+ await config.addComment(entity, closingMessage);
+ core.info(` ✓ Comment added successfully`);
+
+ // Step 4: Close entity
+ core.info(` Closing ${config.entityType} #${entity.number}`);
+ await config.closeEntity(entity);
+ core.info(` ✓ ${capitalize(config.entityType)} closed successfully`);
+
+ // Step 5: Return status and record
+ return {
+ status: "closed",
+ record: {
+ number: entity.number,
+ url: entity.url,
+ title: entity.title,
+ },
+ };
+ };
+}
+
+/**
+ * Capitalize the first letter of a string
+ * @param {string} str - String to capitalize
+ * @returns {string}
+ */
+function capitalize(str) {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+}
+
+module.exports = {
+ createExpiredEntityProcessor,
+};
diff --git a/actions/setup/js/expired_entity_handlers.test.cjs b/actions/setup/js/expired_entity_handlers.test.cjs
new file mode 100644
index 0000000000..f7fc2a2acd
--- /dev/null
+++ b/actions/setup/js/expired_entity_handlers.test.cjs
@@ -0,0 +1,274 @@
+// @ts-check
+import { describe, it, expect, beforeEach, vi } from "vitest";
+
+// Mock core global
+const mockCore = {
+ info: vi.fn(),
+ warning: vi.fn(),
+ error: vi.fn(),
+};
+
+global.core = mockCore;
+
+describe("expired_entity_handlers", () => {
+ let createExpiredEntityProcessor;
+
+ beforeEach(async () => {
+ vi.clearAllMocks();
+ const module = await import("./expired_entity_handlers.cjs");
+ createExpiredEntityProcessor = module.createExpiredEntityProcessor;
+ });
+
+ describe("createExpiredEntityProcessor", () => {
+ it("should process entity with comment and close", async () => {
+ const mockAddComment = vi.fn().mockResolvedValue({});
+ const mockCloseEntity = vi.fn().mockResolvedValue({});
+ const mockBuildMessage = vi.fn().mockReturnValue("Test closing message");
+
+ const processor = createExpiredEntityProcessor("test-workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow-id", {
+ entityType: "issue",
+ addComment: mockAddComment,
+ closeEntity: mockCloseEntity,
+ buildClosingMessage: mockBuildMessage,
+ });
+
+ const entity = {
+ number: 42,
+ url: "https://github.com/test/repo/issues/42",
+ title: "Test Issue",
+ expirationDate: new Date("2020-01-20T09:20:00.000Z"),
+ };
+
+ const result = await processor(entity);
+
+ expect(result).toEqual({
+ status: "closed",
+ record: {
+ number: 42,
+ url: "https://github.com/test/repo/issues/42",
+ title: "Test Issue",
+ },
+ });
+
+ expect(mockBuildMessage).toHaveBeenCalledWith(entity, "test-workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow-id");
+ expect(mockAddComment).toHaveBeenCalledWith(entity, "Test closing message");
+ expect(mockCloseEntity).toHaveBeenCalledWith(entity);
+ expect(mockCore.info).toHaveBeenCalledWith(" Adding closing comment to issue #42");
+ expect(mockCore.info).toHaveBeenCalledWith(" ✓ Comment added successfully");
+ expect(mockCore.info).toHaveBeenCalledWith(" Closing issue #42");
+ expect(mockCore.info).toHaveBeenCalledWith(" ✓ Issue closed successfully");
+ });
+
+ it("should skip entity when preCheck returns shouldSkip=true without closing", async () => {
+ const mockAddComment = vi.fn();
+ const mockCloseEntity = vi.fn();
+ const mockBuildMessage = vi.fn();
+ const mockPreCheck = vi.fn().mockResolvedValue({
+ shouldSkip: true,
+ reason: "Already closed",
+ });
+
+ const processor = createExpiredEntityProcessor("test-workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow-id", {
+ entityType: "discussion",
+ addComment: mockAddComment,
+ closeEntity: mockCloseEntity,
+ buildClosingMessage: mockBuildMessage,
+ preCheck: mockPreCheck,
+ });
+
+ const entity = {
+ number: 99,
+ url: "https://github.com/test/repo/discussions/99",
+ title: "Already Closed",
+ id: "D_test123",
+ };
+
+ const result = await processor(entity);
+
+ expect(result).toEqual({
+ status: "skipped",
+ record: {
+ number: 99,
+ url: "https://github.com/test/repo/discussions/99",
+ title: "Already Closed",
+ },
+ });
+
+ expect(mockPreCheck).toHaveBeenCalledWith(entity);
+ expect(mockCore.warning).toHaveBeenCalledWith(" Already closed");
+ expect(mockAddComment).not.toHaveBeenCalled();
+ expect(mockCloseEntity).not.toHaveBeenCalled();
+ });
+
+ it("should skip entity and close when preCheck returns shouldSkip=true with shouldClose=true", async () => {
+ const mockAddComment = vi.fn();
+ const mockCloseEntity = vi.fn().mockResolvedValue({});
+ const mockBuildMessage = vi.fn();
+ const mockPreCheck = vi.fn().mockResolvedValue({
+ shouldSkip: true,
+ shouldClose: true,
+ reason: "Has existing comment, closing without adding another",
+ });
+
+ const processor = createExpiredEntityProcessor("test-workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow-id", {
+ entityType: "discussion",
+ addComment: mockAddComment,
+ closeEntity: mockCloseEntity,
+ buildClosingMessage: mockBuildMessage,
+ preCheck: mockPreCheck,
+ });
+
+ const entity = {
+ number: 88,
+ url: "https://github.com/test/repo/discussions/88",
+ title: "Has Comment",
+ id: "D_test456",
+ };
+
+ const result = await processor(entity);
+
+ expect(result).toEqual({
+ status: "skipped",
+ record: {
+ number: 88,
+ url: "https://github.com/test/repo/discussions/88",
+ title: "Has Comment",
+ },
+ });
+
+ expect(mockPreCheck).toHaveBeenCalledWith(entity);
+ expect(mockCore.warning).toHaveBeenCalledWith(" Has existing comment, closing without adding another");
+ expect(mockCore.info).toHaveBeenCalledWith(" Attempting to close discussion #88 without adding another comment");
+ expect(mockCloseEntity).toHaveBeenCalledWith(entity);
+ expect(mockCore.info).toHaveBeenCalledWith(" ✓ Discussion closed successfully");
+ expect(mockAddComment).not.toHaveBeenCalled();
+ });
+
+ it("should process entity when preCheck returns shouldSkip=false", async () => {
+ const mockAddComment = vi.fn().mockResolvedValue({});
+ const mockCloseEntity = vi.fn().mockResolvedValue({});
+ const mockBuildMessage = vi.fn().mockReturnValue("Closing message");
+ const mockPreCheck = vi.fn().mockResolvedValue({
+ shouldSkip: false,
+ });
+
+ const processor = createExpiredEntityProcessor("test-workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow-id", {
+ entityType: "discussion",
+ addComment: mockAddComment,
+ closeEntity: mockCloseEntity,
+ buildClosingMessage: mockBuildMessage,
+ preCheck: mockPreCheck,
+ });
+
+ const entity = {
+ number: 77,
+ url: "https://github.com/test/repo/discussions/77",
+ title: "No Comment",
+ id: "D_test789",
+ };
+
+ const result = await processor(entity);
+
+ expect(result).toEqual({
+ status: "closed",
+ record: {
+ number: 77,
+ url: "https://github.com/test/repo/discussions/77",
+ title: "No Comment",
+ },
+ });
+
+ expect(mockPreCheck).toHaveBeenCalledWith(entity);
+ expect(mockAddComment).toHaveBeenCalledWith(entity, "Closing message");
+ expect(mockCloseEntity).toHaveBeenCalledWith(entity);
+ });
+
+ it("should work without preCheck function", async () => {
+ const mockAddComment = vi.fn().mockResolvedValue({});
+ const mockCloseEntity = vi.fn().mockResolvedValue({});
+ const mockBuildMessage = vi.fn().mockReturnValue("Simple close");
+
+ const processor = createExpiredEntityProcessor("test-workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow-id", {
+ entityType: "pull request",
+ addComment: mockAddComment,
+ closeEntity: mockCloseEntity,
+ buildClosingMessage: mockBuildMessage,
+ // No preCheck provided
+ });
+
+ const entity = {
+ number: 55,
+ url: "https://github.com/test/repo/pull/55",
+ title: "Simple PR",
+ };
+
+ const result = await processor(entity);
+
+ expect(result).toEqual({
+ status: "closed",
+ record: {
+ number: 55,
+ url: "https://github.com/test/repo/pull/55",
+ title: "Simple PR",
+ },
+ });
+
+ expect(mockAddComment).toHaveBeenCalledWith(entity, "Simple close");
+ expect(mockCloseEntity).toHaveBeenCalledWith(entity);
+ });
+
+ it("should capitalize entity type in log messages", async () => {
+ const mockAddComment = vi.fn().mockResolvedValue({});
+ const mockCloseEntity = vi.fn().mockResolvedValue({});
+ const mockBuildMessage = vi.fn().mockReturnValue("Test");
+
+ const processor = createExpiredEntityProcessor("test-workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow-id", {
+ entityType: "pull request",
+ addComment: mockAddComment,
+ closeEntity: mockCloseEntity,
+ buildClosingMessage: mockBuildMessage,
+ });
+
+ const entity = {
+ number: 1,
+ url: "https://github.com/test/repo/pull/1",
+ title: "Test",
+ };
+
+ await processor(entity);
+
+ expect(mockCore.info).toHaveBeenCalledWith(" Adding closing comment to pull request #1");
+ expect(mockCore.info).toHaveBeenCalledWith(" ✓ Pull request closed successfully");
+ });
+
+ it("should handle all three entity types correctly", async () => {
+ const entities = [
+ { type: "issue", number: 1, url: "https://github.com/test/repo/issues/1", title: "Issue" },
+ { type: "pull request", number: 2, url: "https://github.com/test/repo/pull/2", title: "PR" },
+ { type: "discussion", number: 3, url: "https://github.com/test/repo/discussions/3", title: "Discussion" },
+ ];
+
+ for (const entity of entities) {
+ vi.clearAllMocks();
+
+ const mockAddComment = vi.fn().mockResolvedValue({});
+ const mockCloseEntity = vi.fn().mockResolvedValue({});
+ const mockBuildMessage = vi.fn().mockReturnValue("Close message");
+
+ const processor = createExpiredEntityProcessor("test-workflow", "https://github.com/test/repo/actions/runs/123", "test-workflow-id", {
+ entityType: entity.type,
+ addComment: mockAddComment,
+ closeEntity: mockCloseEntity,
+ buildClosingMessage: mockBuildMessage,
+ });
+
+ const result = await processor(entity);
+
+ expect(result.status).toBe("closed");
+ expect(result.record.number).toBe(entity.number);
+ expect(mockAddComment).toHaveBeenCalled();
+ expect(mockCloseEntity).toHaveBeenCalled();
+ }
+ });
+ });
+});