Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/specification/draft/schema.mdx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 19 additions & 8 deletions tools/sep-automation/src/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
type SEPItem,
type ActionResult,
type SEPState,
type StaleAnalysis,
} from "./types.js";

/** Summary data collected during processing */
Expand Down Expand Up @@ -95,10 +96,14 @@ export class SEPProcessor {
}

// Check staleness
const stalenessResult = await this.checkStaleness(sep);
if (stalenessResult) {
results.push(stalenessResult);
this.updateSummaryFromStaleness(stalenessResult, sep, summaryData);
const stalenessAction = await this.checkStaleness(sep);
if (stalenessAction) {
results.push(stalenessAction.result);
await this.updateSummaryFromStaleness(
stalenessAction.result,
stalenessAction.analysis,
summaryData,
);
}

// Check maintainer accountability
Expand Down Expand Up @@ -155,14 +160,20 @@ export class SEPProcessor {
/**
* Check for staleness and take appropriate action
*/
private async checkStaleness(sep: SEPItem): Promise<ActionResult | null> {
private async checkStaleness(
sep: SEPItem,
): Promise<{ result: ActionResult; analysis: StaleAnalysis } | null> {
const analysis = await this.analyzer.analyze(sep);

if (!analysis.shouldPing && !analysis.shouldMarkDormant) {
return null;
}

return this.pingHandler.executePing(analysis, this.config.dryRun);
const result = await this.pingHandler.executePing(
analysis,
this.config.dryRun,
);
return { result, analysis };
}

/**
Expand Down Expand Up @@ -218,14 +229,14 @@ export class SEPProcessor {
*/
private async updateSummaryFromStaleness(
result: ActionResult,
sep: SEPItem,
analysis: StaleAnalysis,
summary: SummaryData,
): Promise<void> {
if (!result.success) {
return;
}

const analysis = await this.analyzer.analyze(sep);
const { item: sep } = analysis;

switch (result.action.type) {
case ActionType.NeedsSponsor:
Expand Down
112 changes: 112 additions & 0 deletions tools/sep-automation/test/unit/processor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { PingHandler } from "../../src/actions/ping.js";
import type { TransitionHandler } from "../../src/actions/transition.js";
import type { GitHubComment } from "../../src/github/types.js";
import { SEPProcessor } from "../../src/processor.js";
import { SEPAnalyzer } from "../../src/sep/analyzer.js";
import { ActionType, BOT_COMMENT_MARKER } from "../../src/types.js";
import {
asGitHubClient,
asLogger,
asMaintainerResolver,
createMockConfig,
createMockGitHubClient,
createMockLogger,
createMockMaintainerResolver,
createMockSEPItem,
type MockGitHubClient,
type MockLogger,
type MockMaintainerResolver,
} from "../mocks.js";

const MS_PER_DAY = 24 * 60 * 60 * 1000;

describe("SEPProcessor", () => {
let github: MockGitHubClient;
let maintainers: MockMaintainerResolver;
let logger: MockLogger;

beforeEach(() => {
vi.clearAllMocks();
github = createMockGitHubClient();
maintainers = createMockMaintainerResolver();
logger = createMockLogger();
});

function createProcessor(comments: GitHubComment[]): SEPProcessor {
const config = createMockConfig();
const githubClient = asGitHubClient(github);
const maintainerResolver = asMaintainerResolver(maintainers);
const typedLogger = asLogger(logger);

github.getComments.mockImplementation(async () => comments);
github.addComment.mockImplementation(async (_number, body: string) => {
comments.push({
id: comments.length + 1,
body,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
user: { login: "sep-automation-bot" },
});
return { url: "https://github.com/comment/1" };
});

return new SEPProcessor(
config,
new SEPAnalyzer(config, githubClient),
maintainerResolver,
{
executeTransition: vi.fn(),
} as unknown as TransitionHandler,
new PingHandler(config, githubClient, maintainerResolver, typedLogger),
typedLogger,
);
}

it("includes a successful stale ping in the returned summary", async () => {
const comments: GitHubComment[] = [];
const processor = createProcessor(comments);
const sep = createMockSEPItem({
assignees: [],
createdAt: new Date(Date.now() - 95 * MS_PER_DAY),
});

const result = await processor.process(sep);

expect(result.results).toHaveLength(1);
expect(result.results[0]?.action.type).toBe(ActionType.PingAuthor);
expect(result.summaryData.pings).toEqual([
{
item: sep,
pingTarget: "author",
targetUser: sep.author,
daysSinceActivity: 95,
},
]);
expect(comments[0]?.body).toContain(BOT_COMMENT_MARKER);
expect(github.getComments).toHaveBeenCalledTimes(1);
});

it("preserves the original close decision in the dormant summary", async () => {
const comments: GitHubComment[] = [];
const processor = createProcessor(comments);
const sep = createMockSEPItem({
assignees: [],
createdAt: new Date(Date.now() - 185 * MS_PER_DAY),
});

const result = await processor.process(sep);

expect(result.results).toHaveLength(1);
expect(result.results[0]?.action.type).toBe(ActionType.MarkDormant);
expect(result.summaryData.dormant).toEqual([
{
item: sep,
daysSinceActivity: 185,
wasClosed: true,
},
]);
expect(github.closeIssue).toHaveBeenCalledWith(sep.number);
expect(github.getComments).toHaveBeenCalledTimes(1);
});
});
Loading