1150 lines
36 KiB
TypeScript
1150 lines
36 KiB
TypeScript
import { jest, describe, it, expect, beforeEach, afterEach, beforeAll } from "@jest/globals"
|
|
let createPr: typeof import("../create-pr").createPr
|
|
let triggerCreatePr: typeof import("../create-pr").triggerCreatePr
|
|
let setCreatePrDependencies: typeof import("../create-pr").setCreatePrDependencies
|
|
let resetCreatePrDependencies: typeof import("../create-pr").resetCreatePrDependencies
|
|
let setTriggerCreatePrDependencies: typeof import("../create-pr").setTriggerCreatePrDependencies
|
|
let resetTriggerCreatePrDependencies: typeof import("../create-pr").resetTriggerCreatePrDependencies
|
|
let createStandalonePRTitleAndBody: typeof import("../create-pr").createStandalonePRTitleAndBody
|
|
type PrContentBuilder = import("../create-pr").PrContentBuilder
|
|
// Note: Global mocks are set up in jest.setup.ts
|
|
|
|
describe("createPr", () => {
|
|
beforeAll(async () => {
|
|
process.env.KEY_VAULT_NAME = "mocked-keyvault-name"
|
|
const mod = await import("../create-pr")
|
|
createPr = mod.createPr
|
|
triggerCreatePr = mod.triggerCreatePr
|
|
setCreatePrDependencies = mod.setCreatePrDependencies
|
|
resetCreatePrDependencies = mod.resetCreatePrDependencies
|
|
setTriggerCreatePrDependencies = mod.setTriggerCreatePrDependencies
|
|
resetTriggerCreatePrDependencies = mod.resetTriggerCreatePrDependencies
|
|
createStandalonePRTitleAndBody = mod.createStandalonePRTitleAndBody
|
|
})
|
|
let mockReq: any
|
|
let mockRes: any
|
|
let mockDependencies: any
|
|
|
|
beforeEach(() => {
|
|
// Setup request mock
|
|
mockReq = {
|
|
body: {
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
baseBranch: "main",
|
|
diffContents: [{ file: "test.js", content: "test" }],
|
|
prCommentFields: {
|
|
function_name: "testFunction",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
},
|
|
existingTests: [],
|
|
generatedTests: [],
|
|
coverage_message: "coverage message", // Changed from coverage to coverage_message
|
|
traceId: "test-trace-id",
|
|
},
|
|
userId: "test-user-id",
|
|
}
|
|
|
|
// Setup response mock
|
|
mockRes = {
|
|
status: jest.fn().mockReturnThis(),
|
|
send: jest.fn(),
|
|
json: jest.fn(),
|
|
}
|
|
|
|
// Setup mock dependencies with proper typing - cast to any to avoid complex Prisma types
|
|
mockDependencies = {
|
|
prisma: {
|
|
optimization_features: {
|
|
findUnique: jest.fn(),
|
|
update: jest.fn(),
|
|
},
|
|
} as any, // Cast to any to avoid Prisma type complexity
|
|
userNickname: jest.fn(),
|
|
getInstallationOctokitByOwner: jest.fn(),
|
|
isUserCollaborator: jest.fn(),
|
|
requiresApproval: jest.fn(),
|
|
requestApproval: jest.fn(),
|
|
triggerCreatePr: jest.fn(),
|
|
posthog: {
|
|
capture: jest.fn(),
|
|
},
|
|
isDiffContentsWellFormed: jest.fn().mockReturnValue(true),
|
|
githubApp: {},
|
|
}
|
|
|
|
setCreatePrDependencies(mockDependencies)
|
|
})
|
|
|
|
afterEach(() => {
|
|
resetCreatePrDependencies()
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
describe("input validation", () => {
|
|
it("should return 400 when owner is missing", async () => {
|
|
mockReq.body.owner = undefined
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(400)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Missing or malformed fields")
|
|
})
|
|
|
|
it("should return 400 when repo is missing", async () => {
|
|
mockReq.body.repo = undefined
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(400)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Missing or malformed fields")
|
|
})
|
|
|
|
it("should return 400 when baseBranch is missing", async () => {
|
|
mockReq.body.baseBranch = undefined
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(400)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Missing or malformed fields")
|
|
})
|
|
|
|
it("should return 400 for malformed diff contents", async () => {
|
|
mockDependencies.isDiffContentsWellFormed.mockReturnValue(false)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(400)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Missing or malformed fields")
|
|
expect(mockDependencies.isDiffContentsWellFormed).toHaveBeenCalledWith(
|
|
mockReq.body.diffContents,
|
|
)
|
|
})
|
|
|
|
it("should handle missing traceId by defaulting to empty string", async () => {
|
|
delete mockReq.body.traceId
|
|
mockDependencies.userNickname.mockResolvedValue("test-user")
|
|
mockDependencies.getInstallationOctokitByOwner.mockResolvedValue({})
|
|
mockDependencies.isUserCollaborator.mockResolvedValue(true)
|
|
mockDependencies.requiresApproval.mockReturnValue(false)
|
|
mockDependencies.triggerCreatePr.mockResolvedValue(456)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.triggerCreatePr).toHaveBeenCalledWith(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
mockReq.body.diffContents,
|
|
mockReq.body.prCommentFields,
|
|
mockReq.body.existingTests,
|
|
mockReq.body.generatedTests,
|
|
mockReq.body.coverage_message,
|
|
"test-user-id",
|
|
"test-user",
|
|
{},
|
|
"", // traceId should be empty string
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("authentication and authorization", () => {
|
|
it("should return 401 when user nickname is null", async () => {
|
|
mockDependencies.userNickname.mockResolvedValue(null)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.userNickname).toHaveBeenCalledWith("test-user-id")
|
|
expect(mockRes.status).toHaveBeenCalledWith(401)
|
|
expect(mockRes.json).toHaveBeenCalledWith({ error: "Unauthorized" })
|
|
})
|
|
|
|
it("should return 401 when installation octokit returns Error", async () => {
|
|
mockDependencies.userNickname.mockResolvedValue("test-user")
|
|
mockDependencies.getInstallationOctokitByOwner.mockResolvedValue(
|
|
new Error("Installation not found"),
|
|
)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.getInstallationOctokitByOwner).toHaveBeenCalledWith(
|
|
mockDependencies.githubApp,
|
|
"test-owner",
|
|
"test-repo",
|
|
)
|
|
expect(mockRes.status).toHaveBeenCalledWith(401)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Installation not found")
|
|
})
|
|
|
|
it("should return 401 when user is not a collaborator", async () => {
|
|
mockDependencies.userNickname.mockResolvedValue("test-user")
|
|
mockDependencies.getInstallationOctokitByOwner.mockResolvedValue({})
|
|
mockDependencies.isUserCollaborator.mockResolvedValue(false)
|
|
|
|
const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.isUserCollaborator).toHaveBeenCalledWith(
|
|
{},
|
|
"test-owner",
|
|
"test-repo",
|
|
"test-user",
|
|
)
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
"test-user is not a collaborator on test-owner/test-repo",
|
|
)
|
|
expect(mockRes.status).toHaveBeenCalledWith(401)
|
|
expect(mockRes.json).toHaveBeenCalledWith({ error: "Unauthorized" })
|
|
|
|
consoleSpy.mockRestore()
|
|
})
|
|
})
|
|
|
|
describe("approval workflow", () => {
|
|
beforeEach(() => {
|
|
mockDependencies.userNickname.mockResolvedValue("test-user")
|
|
mockDependencies.getInstallationOctokitByOwner.mockResolvedValue({})
|
|
mockDependencies.isUserCollaborator.mockResolvedValue(true)
|
|
})
|
|
|
|
it("should skip approval check when traceId is missing", async () => {
|
|
mockReq.body.traceId = ""
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.triggerCreatePr.mockResolvedValue(456)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.prisma.optimization_features.findUnique).not.toHaveBeenCalled()
|
|
expect(mockDependencies.triggerCreatePr).toHaveBeenCalled()
|
|
expect(mockRes.json).toHaveBeenCalledWith(456)
|
|
})
|
|
|
|
it("should skip approval check when approval is not required", async () => {
|
|
mockDependencies.requiresApproval.mockReturnValue(false)
|
|
mockDependencies.triggerCreatePr.mockResolvedValue(456)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.prisma.optimization_features.findUnique).not.toHaveBeenCalled()
|
|
expect(mockDependencies.triggerCreatePr).toHaveBeenCalled()
|
|
expect(mockRes.json).toHaveBeenCalledWith(456)
|
|
})
|
|
|
|
it("should return 403 for rejected requests", async () => {
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
approval_status: "rejected",
|
|
approval_required: true,
|
|
})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.prisma.optimization_features.findUnique).toHaveBeenCalledWith({
|
|
where: { trace_id: "test-trace-id" },
|
|
select: {
|
|
approval_required: true,
|
|
approval_status: true,
|
|
},
|
|
})
|
|
expect(mockRes.status).toHaveBeenCalledWith(403)
|
|
expect(mockRes.json).toHaveBeenCalledWith({
|
|
status: "rejected",
|
|
message: "This optimization request was rejected.",
|
|
})
|
|
})
|
|
|
|
it("should proceed with PR creation for approved requests with successful PR", async () => {
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
approval_status: "approved",
|
|
approval_required: true,
|
|
})
|
|
mockDependencies.triggerCreatePr.mockResolvedValue(123)
|
|
|
|
const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
"Request test-trace-id was previously approved, continuing with PR creation",
|
|
)
|
|
expect(mockDependencies.triggerCreatePr).toHaveBeenCalled()
|
|
expect(mockRes.json).toHaveBeenCalledWith(123)
|
|
|
|
consoleSpy.mockRestore()
|
|
})
|
|
|
|
it("should return 500 for approved requests with failed PR creation", async () => {
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
approval_status: "approved",
|
|
approval_required: true,
|
|
})
|
|
mockDependencies.triggerCreatePr.mockResolvedValue(-1)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(500)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Error creating pull request")
|
|
})
|
|
|
|
it("should request approval for pending requests", async () => {
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
approval_status: "pending",
|
|
approval_required: true,
|
|
})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.requestApproval).toHaveBeenCalledWith(
|
|
"test-trace-id",
|
|
"test-owner",
|
|
"test-repo",
|
|
"testFunction",
|
|
"test-user-id",
|
|
{
|
|
type: "create-pr",
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
baseBranch: "main",
|
|
diffContents: mockReq.body.diffContents,
|
|
prCommentFields: mockReq.body.prCommentFields,
|
|
existingTests: [],
|
|
generatedTests: [],
|
|
coverage_message: "coverage message", // Changed from coverage to coverage_message
|
|
userId: "test-user-id",
|
|
},
|
|
)
|
|
expect(mockRes.status).toHaveBeenCalledWith(202)
|
|
expect(mockRes.json).toHaveBeenCalledWith({
|
|
status: "pending_approval",
|
|
message: "This optimization requires approval. You will be notified when it is processed.",
|
|
})
|
|
})
|
|
|
|
it("should request approval for new requests (null approval_status)", async () => {
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
approval_status: null,
|
|
approval_required: true,
|
|
})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.requestApproval).toHaveBeenCalled()
|
|
expect(mockRes.status).toHaveBeenCalledWith(202)
|
|
})
|
|
|
|
it("should handle missing optimization record", async () => {
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.prisma.optimization_features.findUnique.mockResolvedValue(null)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.requestApproval).toHaveBeenCalled()
|
|
expect(mockRes.status).toHaveBeenCalledWith(202)
|
|
})
|
|
})
|
|
|
|
describe("successful PR creation without approval", () => {
|
|
beforeEach(() => {
|
|
mockDependencies.userNickname.mockResolvedValue("test-user")
|
|
mockDependencies.getInstallationOctokitByOwner.mockResolvedValue({})
|
|
mockDependencies.isUserCollaborator.mockResolvedValue(true)
|
|
mockDependencies.requiresApproval.mockReturnValue(false)
|
|
})
|
|
|
|
it("should create PR successfully", async () => {
|
|
mockDependencies.triggerCreatePr.mockResolvedValue(456)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockDependencies.triggerCreatePr).toHaveBeenCalledWith(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
mockReq.body.diffContents,
|
|
mockReq.body.prCommentFields,
|
|
mockReq.body.existingTests,
|
|
mockReq.body.generatedTests,
|
|
mockReq.body.coverage_message, // Changed from coverage to coverage_message
|
|
"test-user-id",
|
|
"test-user",
|
|
{},
|
|
"test-trace-id",
|
|
)
|
|
expect(mockRes.json).toHaveBeenCalledWith(456)
|
|
})
|
|
|
|
it("should return 500 when PR creation fails", async () => {
|
|
mockDependencies.triggerCreatePr.mockResolvedValue(-1)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(500)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Error creating pull request")
|
|
})
|
|
|
|
it("should return 500 when PR creation returns 0", async () => {
|
|
mockDependencies.triggerCreatePr.mockResolvedValue(0)
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(500)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Error creating pull request")
|
|
})
|
|
})
|
|
|
|
describe("error handling", () => {
|
|
it("should handle Error instances and track them", async () => {
|
|
const error = new Error("Test error")
|
|
error.stack = "Test stack trace"
|
|
mockDependencies.userNickname.mockRejectedValue(error)
|
|
|
|
const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith(`Error in /cfapi/create-pr: ${error}`)
|
|
expect(consoleSpy).toHaveBeenCalledWith(`Error message: ${error.message}`)
|
|
expect(consoleSpy).toHaveBeenCalledWith(`Error stack: ${error.stack}`)
|
|
expect(mockDependencies.posthog.capture).toHaveBeenCalledWith({
|
|
distinctId: "test-user-id",
|
|
event: "cfapi-create-pr-failed-error-creating-standalone-pr",
|
|
properties: {
|
|
error: error.message,
|
|
},
|
|
})
|
|
expect(mockRes.status).toHaveBeenCalledWith(500)
|
|
expect(mockRes.send).toHaveBeenCalledWith(`Error creating pull request: ${error.message}`)
|
|
|
|
consoleSpy.mockRestore()
|
|
})
|
|
|
|
it("should handle non-Error exceptions", async () => {
|
|
mockDependencies.userNickname.mockRejectedValue("String error")
|
|
|
|
const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(consoleSpy).toHaveBeenCalledWith("Error in /cfapi/create-pr: String error")
|
|
expect(mockRes.status).toHaveBeenCalledWith(500)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Error creating pull request")
|
|
|
|
consoleSpy.mockRestore()
|
|
})
|
|
|
|
it("should handle errors in database queries during approval check", async () => {
|
|
mockDependencies.userNickname.mockResolvedValue("test-user")
|
|
mockDependencies.getInstallationOctokitByOwner.mockResolvedValue({})
|
|
mockDependencies.isUserCollaborator.mockResolvedValue(true)
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.prisma.optimization_features.findUnique.mockRejectedValue(
|
|
new Error("Database error"),
|
|
)
|
|
|
|
const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(500)
|
|
expect(mockRes.send).toHaveBeenCalledWith("Error creating pull request: Database error")
|
|
|
|
consoleSpy.mockRestore()
|
|
})
|
|
|
|
it("should handle errors in requestApproval", async () => {
|
|
mockDependencies.userNickname.mockResolvedValue("test-user")
|
|
mockDependencies.getInstallationOctokitByOwner.mockResolvedValue({})
|
|
mockDependencies.isUserCollaborator.mockResolvedValue(true)
|
|
mockDependencies.requiresApproval.mockReturnValue(true)
|
|
mockDependencies.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
approval_status: "pending",
|
|
})
|
|
mockDependencies.requestApproval.mockRejectedValue(new Error("Approval service error"))
|
|
|
|
const consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {})
|
|
|
|
await createPr(mockReq, mockRes)
|
|
|
|
expect(mockRes.status).toHaveBeenCalledWith(500)
|
|
expect(mockRes.send).toHaveBeenCalledWith(
|
|
"Error creating pull request: Approval service error",
|
|
)
|
|
|
|
consoleSpy.mockRestore()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("triggerCreatePr", () => {
|
|
let mockDeps: any
|
|
let mockInstallationOctokit: any
|
|
let consoleSpy: any
|
|
|
|
beforeEach(() => {
|
|
// Mock console.log to prevent noisy output during tests
|
|
consoleSpy = jest.spyOn(console, "log").mockImplementation(() => {})
|
|
|
|
mockInstallationOctokit = { rest: { pulls: {} } }
|
|
|
|
const mockPrContentBuilder: PrContentBuilder = {
|
|
buildResultHeader: jest.fn(() => "## Header"),
|
|
buildBenchmarkInfo: jest.fn(() => ""),
|
|
buildResultDetails: jest.fn(() => "Details"),
|
|
buildResultTestReport: jest.fn(() => "Test report"),
|
|
buildResultFooter: jest.fn(() => "Footer"),
|
|
buildPrTitle: jest.fn(() => "Test PR Title"),
|
|
}
|
|
|
|
mockDeps = {
|
|
prisma: {
|
|
optimization_events: {
|
|
update: jest.fn(),
|
|
},
|
|
optimization_features: {
|
|
findUnique: jest.fn(),
|
|
update: jest.fn(),
|
|
},
|
|
} as any,
|
|
fileDiffsToMap: jest.fn().mockReturnValue(new Map()),
|
|
buildPrTitle: jest.fn().mockReturnValue("Test PR Title"),
|
|
createNewBranchFromDiffContents: jest.fn(),
|
|
createStandalonePullRequest: jest.fn(),
|
|
addLabelToPullRequest: jest.fn(),
|
|
assignReviewer: jest.fn(),
|
|
posthog: {
|
|
capture: jest.fn(),
|
|
},
|
|
createStandalonePRTitleAndBody: jest.fn().mockReturnValue({
|
|
title: "Test PR Title",
|
|
body: "Test PR Body",
|
|
}),
|
|
prContentBuilder: mockPrContentBuilder,
|
|
}
|
|
|
|
setTriggerCreatePrDependencies(mockDeps)
|
|
})
|
|
|
|
afterEach(() => {
|
|
resetTriggerCreatePrDependencies()
|
|
jest.clearAllMocks()
|
|
consoleSpy.mockRestore()
|
|
})
|
|
|
|
describe("successful PR creation", () => {
|
|
it("should successfully create a PR with traceId", async () => {
|
|
const mockPrCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
}
|
|
|
|
mockDeps.createNewBranchFromDiffContents.mockResolvedValue(true)
|
|
mockDeps.createStandalonePullRequest.mockResolvedValue({
|
|
data: {
|
|
id: 123,
|
|
number: 456,
|
|
html_url: "https://github.com/test/test/pull/456",
|
|
head: { ref: "test-branch" },
|
|
},
|
|
})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
mockPrCommentFields,
|
|
"existing tests",
|
|
"generated tests",
|
|
"coverage message",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
"trace123",
|
|
)
|
|
|
|
expect(result).toBe(456)
|
|
expect(mockDeps.fileDiffsToMap).toHaveBeenCalledWith([])
|
|
expect(mockDeps.buildPrTitle).toHaveBeenCalledWith("testFunc", 50, 2)
|
|
expect(mockDeps.createNewBranchFromDiffContents).toHaveBeenCalledWith(
|
|
mockInstallationOctokit,
|
|
"test-owner",
|
|
"test-repo",
|
|
expect.stringContaining("codeflash/optimize-testFunc-"),
|
|
"main",
|
|
expect.any(Map),
|
|
"Test PR Title\nTest optimization",
|
|
)
|
|
expect(mockDeps.createStandalonePRTitleAndBody).toHaveBeenCalledWith(
|
|
mockPrCommentFields,
|
|
"existing tests",
|
|
"generated tests",
|
|
"coverage message",
|
|
expect.stringContaining("codeflash/optimize-testFunc-"),
|
|
mockDeps.prContentBuilder,
|
|
)
|
|
expect(mockDeps.createStandalonePullRequest).toHaveBeenCalledWith(
|
|
mockInstallationOctokit,
|
|
"test-owner",
|
|
"test-repo",
|
|
"Test PR Title",
|
|
"Test PR Body",
|
|
expect.stringContaining("codeflash/optimize-testFunc-"),
|
|
"main",
|
|
)
|
|
expect(mockDeps.addLabelToPullRequest).toHaveBeenCalledWith(
|
|
mockInstallationOctokit,
|
|
"test-owner",
|
|
"test-repo",
|
|
456,
|
|
)
|
|
expect(mockDeps.assignReviewer).toHaveBeenCalledWith(
|
|
mockInstallationOctokit,
|
|
"test-owner",
|
|
"test-repo",
|
|
456,
|
|
"testuser",
|
|
)
|
|
expect(consoleSpy).toHaveBeenCalledWith("Created new PR #456 with branch test-branch")
|
|
expect(mockDeps.posthog.capture).toHaveBeenCalledWith({
|
|
distinctId: "user123",
|
|
event: "cfapi-create-pr-success-standalone-pr-created",
|
|
properties: {
|
|
owner: "test-owner",
|
|
repo: "test-repo",
|
|
newPrNumber: 456,
|
|
newPrBranch: "test-branch",
|
|
PRURL: "https://github.com/test/test/pull/456",
|
|
},
|
|
})
|
|
})
|
|
|
|
it("should successfully create a PR without traceId", async () => {
|
|
const mockPrCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
}
|
|
|
|
mockDeps.createNewBranchFromDiffContents.mockResolvedValue(true)
|
|
mockDeps.createStandalonePullRequest.mockResolvedValue({
|
|
data: {
|
|
id: 123,
|
|
number: 456,
|
|
html_url: "https://github.com/test/test/pull/456",
|
|
head: { ref: "test-branch" },
|
|
},
|
|
})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
mockPrCommentFields,
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
"", // empty traceId
|
|
)
|
|
|
|
expect(result).toBe(456)
|
|
expect(mockDeps.prisma.optimization_events.update).toHaveBeenCalledWith({
|
|
where: { trace_id: "" },
|
|
data: {
|
|
pr_id: "123",
|
|
is_optimization_found: true,
|
|
event_type: "pr_created",
|
|
},
|
|
})
|
|
// Should not call optimization_features operations when traceId is empty
|
|
expect(mockDeps.prisma.optimization_features.findUnique).not.toHaveBeenCalled()
|
|
expect(mockDeps.prisma.optimization_features.update).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe("branch creation failure", () => {
|
|
it("should return -1 when branch creation fails", async () => {
|
|
mockDeps.createNewBranchFromDiffContents.mockResolvedValue(false)
|
|
|
|
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
{ function_name: "testFunc" },
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
)
|
|
|
|
expect(result).toBe(-1)
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining("Error in triggerCreatePr:"),
|
|
)
|
|
|
|
consoleErrorSpy.mockRestore()
|
|
})
|
|
})
|
|
|
|
describe("optimization events handling", () => {
|
|
beforeEach(() => {
|
|
mockDeps.createNewBranchFromDiffContents.mockResolvedValue(true)
|
|
mockDeps.createStandalonePullRequest.mockResolvedValue({
|
|
data: {
|
|
id: 123,
|
|
number: 456,
|
|
html_url: "https://github.com/test/test/pull/456",
|
|
head: { ref: "test-branch" },
|
|
},
|
|
})
|
|
})
|
|
|
|
it("should handle optimization_events update failure gracefully", async () => {
|
|
const mockPrCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
}
|
|
|
|
mockDeps.prisma.optimization_events.update.mockRejectedValue(new Error("DB error"))
|
|
|
|
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
mockPrCommentFields,
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
"trace123",
|
|
)
|
|
|
|
expect(result).toBe(456)
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
"Failed to update optimization event:",
|
|
expect.any(Error),
|
|
)
|
|
|
|
consoleErrorSpy.mockRestore()
|
|
})
|
|
})
|
|
|
|
describe("optimization features handling", () => {
|
|
beforeEach(() => {
|
|
mockDeps.createNewBranchFromDiffContents.mockResolvedValue(true)
|
|
mockDeps.createStandalonePullRequest.mockResolvedValue({
|
|
data: {
|
|
id: 123,
|
|
number: 456,
|
|
html_url: "https://github.com/test/test/pull/456",
|
|
head: { ref: "test-branch" },
|
|
},
|
|
})
|
|
})
|
|
|
|
it("should update optimization_features when traceId exists and record found", async () => {
|
|
const mockPrCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
}
|
|
|
|
mockDeps.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
pull_request: { existing: "data" },
|
|
})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
mockPrCommentFields,
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
"trace123",
|
|
)
|
|
|
|
expect(result).toBe(456)
|
|
expect(mockDeps.prisma.optimization_features.findUnique).toHaveBeenCalledWith({
|
|
where: { trace_id: "trace123" },
|
|
select: { pull_request: true },
|
|
})
|
|
expect(mockDeps.prisma.optimization_features.update).toHaveBeenCalledWith({
|
|
where: { trace_id: "trace123" },
|
|
data: {
|
|
pull_request: {
|
|
existing: "data",
|
|
new_pr_url: "https://github.com/test/test/pull/456",
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
it("should handle null pull_request in optimization_features", async () => {
|
|
const mockPrCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
}
|
|
|
|
mockDeps.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
pull_request: null,
|
|
})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
mockPrCommentFields,
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
"trace123",
|
|
)
|
|
|
|
expect(result).toBe(456)
|
|
expect(mockDeps.prisma.optimization_features.update).toHaveBeenCalledWith({
|
|
where: { trace_id: "trace123" },
|
|
data: {
|
|
pull_request: {
|
|
new_pr_url: "https://github.com/test/test/pull/456",
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
it("should handle undefined pull_request in optimization_features", async () => {
|
|
const mockPrCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
}
|
|
|
|
mockDeps.prisma.optimization_features.findUnique.mockResolvedValue({
|
|
pull_request: undefined,
|
|
})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
mockPrCommentFields,
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
"trace123",
|
|
)
|
|
|
|
expect(result).toBe(456)
|
|
expect(mockDeps.prisma.optimization_features.update).toHaveBeenCalledWith({
|
|
where: { trace_id: "trace123" },
|
|
data: {
|
|
pull_request: {
|
|
new_pr_url: "https://github.com/test/test/pull/456",
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
it("should handle when optimization_features record is not found", async () => {
|
|
const mockPrCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
}
|
|
|
|
mockDeps.prisma.optimization_features.findUnique.mockResolvedValue(null)
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
mockPrCommentFields,
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
"trace123",
|
|
)
|
|
|
|
expect(result).toBe(456)
|
|
expect(mockDeps.prisma.optimization_features.findUnique).toHaveBeenCalled()
|
|
expect(mockDeps.prisma.optimization_features.update).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe("error scenarios", () => {
|
|
it("should handle errors in PR creation process", async () => {
|
|
mockDeps.createNewBranchFromDiffContents.mockResolvedValue(true)
|
|
mockDeps.createStandalonePullRequest.mockRejectedValue(new Error("PR creation failed"))
|
|
|
|
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
{ function_name: "testFunc" },
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
)
|
|
|
|
expect(result).toBe(-1)
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining("Error in triggerCreatePr:"),
|
|
)
|
|
|
|
consoleErrorSpy.mockRestore()
|
|
})
|
|
|
|
it("should handle errors in labeling PR", async () => {
|
|
mockDeps.createNewBranchFromDiffContents.mockResolvedValue(true)
|
|
mockDeps.createStandalonePullRequest.mockResolvedValue({
|
|
data: {
|
|
id: 123,
|
|
number: 456,
|
|
html_url: "https://github.com/test/test/pull/456",
|
|
head: { ref: "test-branch" },
|
|
},
|
|
})
|
|
mockDeps.addLabelToPullRequest.mockRejectedValue(new Error("Label failed"))
|
|
|
|
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
{ function_name: "testFunc" },
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
)
|
|
|
|
expect(result).toBe(-1)
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining("Error in triggerCreatePr:"),
|
|
)
|
|
|
|
consoleErrorSpy.mockRestore()
|
|
})
|
|
|
|
it("should handle errors in assigning reviewer", async () => {
|
|
mockDeps.createNewBranchFromDiffContents.mockResolvedValue(true)
|
|
mockDeps.createStandalonePullRequest.mockResolvedValue({
|
|
data: {
|
|
id: 123,
|
|
number: 456,
|
|
html_url: "https://github.com/test/test/pull/456",
|
|
head: { ref: "test-branch" },
|
|
},
|
|
})
|
|
mockDeps.addLabelToPullRequest.mockResolvedValue(undefined)
|
|
mockDeps.assignReviewer.mockRejectedValue(new Error("Reviewer assignment failed"))
|
|
|
|
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
|
|
|
|
const result = await triggerCreatePr(
|
|
"test-owner",
|
|
"test-repo",
|
|
"main",
|
|
[],
|
|
{ function_name: "testFunc" },
|
|
"",
|
|
"",
|
|
"",
|
|
"user123",
|
|
"testuser",
|
|
mockInstallationOctokit,
|
|
)
|
|
|
|
expect(result).toBe(-1)
|
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining("Error in triggerCreatePr:"),
|
|
)
|
|
|
|
consoleErrorSpy.mockRestore()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("createStandalonePRTitleAndBody", () => {
|
|
let mockBuilder: PrContentBuilder
|
|
|
|
beforeEach(() => {
|
|
mockBuilder = {
|
|
buildResultHeader: jest.fn(() => "## Header"),
|
|
buildBenchmarkInfo: jest.fn(() => ""),
|
|
buildResultDetails: jest.fn(() => "Details"),
|
|
buildResultTestReport: jest.fn(() => "Test report"),
|
|
buildResultFooter: jest.fn(() => "Footer"),
|
|
buildPrTitle: jest.fn(() => "Test PR Title"),
|
|
}
|
|
})
|
|
|
|
it("should generate correct title and body without benchmark info", () => {
|
|
const prCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
// No benchmark_details property
|
|
} as any
|
|
|
|
const result = createStandalonePRTitleAndBody(
|
|
prCommentFields,
|
|
"existing tests",
|
|
"generated tests",
|
|
"coverage",
|
|
"branch-name",
|
|
mockBuilder,
|
|
)
|
|
|
|
expect(result.title).toBe("Test PR Title")
|
|
expect(result.body).toBe("## Header\nDetails\nTest report\nFooter")
|
|
expect(mockBuilder.buildPrTitle).toHaveBeenCalledWith("testFunc", 50, 2)
|
|
expect(mockBuilder.buildResultHeader).toHaveBeenCalledWith(prCommentFields)
|
|
expect(mockBuilder.buildResultDetails).toHaveBeenCalledWith(prCommentFields)
|
|
expect(mockBuilder.buildResultTestReport).toHaveBeenCalledWith(
|
|
prCommentFields,
|
|
"existing tests",
|
|
"generated tests",
|
|
"coverage",
|
|
)
|
|
expect(mockBuilder.buildResultFooter).toHaveBeenCalledWith("branch-name")
|
|
// buildBenchmarkInfo should NOT be called when benchmark_details is undefined
|
|
expect(mockBuilder.buildBenchmarkInfo).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it("should generate correct title and body with benchmark info", () => {
|
|
mockBuilder.buildBenchmarkInfo = jest.fn(() => "Benchmark data")
|
|
|
|
const prCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
benchmark_details: [
|
|
{
|
|
benchmark_name: "test",
|
|
test_function: "func",
|
|
original_timing: 100,
|
|
expected_new_timing: 50,
|
|
speedup_percent: 50,
|
|
},
|
|
],
|
|
} as any
|
|
|
|
const result = createStandalonePRTitleAndBody(
|
|
prCommentFields,
|
|
"existing tests",
|
|
"generated tests",
|
|
"coverage",
|
|
"branch-name",
|
|
mockBuilder,
|
|
)
|
|
|
|
expect(result.title).toBe("Test PR Title")
|
|
expect(result.body).toBe("## Header\nBenchmark data\nDetails\nTest report\nFooter")
|
|
expect(mockBuilder.buildBenchmarkInfo).toHaveBeenCalledWith(prCommentFields)
|
|
})
|
|
|
|
it("should handle empty benchmark details array", () => {
|
|
const prCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
benchmark_details: [],
|
|
} as any
|
|
|
|
const result = createStandalonePRTitleAndBody(
|
|
prCommentFields,
|
|
"existing tests",
|
|
"generated tests",
|
|
"coverage",
|
|
"branch-name",
|
|
mockBuilder,
|
|
)
|
|
|
|
expect(result.body).toBe("## Header\nDetails\nTest report\nFooter")
|
|
// buildBenchmarkInfo should NOT be called when benchmark_details is empty array
|
|
expect(mockBuilder.buildBenchmarkInfo).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it("should handle null benchmark_details", () => {
|
|
const prCommentFields = {
|
|
function_name: "testFunc",
|
|
speedup_pct: 50,
|
|
speedup_x: 2,
|
|
optimization_explanation: "Test optimization",
|
|
benchmark_details: null,
|
|
} as any
|
|
|
|
const result = createStandalonePRTitleAndBody(
|
|
prCommentFields,
|
|
"existing tests",
|
|
"generated tests",
|
|
"coverage",
|
|
"branch-name",
|
|
mockBuilder,
|
|
)
|
|
|
|
expect(result.body).toBe("## Header\nDetails\nTest report\nFooter")
|
|
// buildBenchmarkInfo should NOT be called when benchmark_details is null
|
|
expect(mockBuilder.buildBenchmarkInfo).not.toHaveBeenCalled()
|
|
})
|
|
})
|