/** * PRIX AI Evaluation System - Mock GitHub Layer * Intercepts GitHub API calls for local testing without external dependencies */ import * as fs from 'fs' import * as path from 'path' import {ReviewComment, MockPRContext} from './types' export interface MockedCall { method: string endpoint: string payload: any timestamp: number } export class MockGitHubLayer { private calls: MockedCall[] = [] private comments: ReviewComment[] = [] private createdPRs: any[] = [] private context: MockPRContext private logFile: string | null = null constructor(context: MockPRContext, options?: {logFile?: string}) { this.context = context if (options?.logFile) { this.logFile = options.logFile } } /** * Create a mock Octokit-like object that captures all API calls */ createMockOctokit(): any { return { issues: { createComment: async (params: any) => { this.recordCall('POST', 'issues.createComment', params) this.comments.push({ path: params.path || 'general', line: params.line || 0, body: params.body, severity: 'info' }) this.log(`đŸ’Ŧ Comment created on ${params.path}:${params.line}`) return {data: {id: Date.now(), ...params}} } }, pulls: { create: async (params: any) => { this.recordCall('POST', 'pulls.create', params) const pr = { number: Date.now(), html_url: `https://github.com/mock/repo/pull/${Date.now()}`, ...params } this.createdPRs.push(pr) this.log(`🔀 PR created: ${pr.title} (draft: ${params.draft})`) return {data: pr} }, update: async (params: any) => { this.recordCall('PATCH', 'pulls.update', params) this.log(`📝 PR #${params.pull_number} updated`) return {data: {...params}} }, list: async (params: any) => { this.recordCall('GET', 'pulls.list', params) return {data: this.createdPRs.filter(pr => pr.state === params.state)} } }, repos: { getContent: async (params: any) => { this.recordCall('GET', 'repos.getContent', params) // Return mock content from local files try { const filePath = path.join(this.context.head.ref, params.path) if (fs.existsSync(filePath)) { const content = fs.readFileSync(filePath, 'utf8') return { data: { content: Buffer.from(content).toString('base64'), encoding: 'base64' } } } } catch (e) { // File not found, return empty } return {data: null} } }, rest: { pulls: { listFiles: async (params: any) => { this.recordCall('GET', 'pulls.listFiles', params) return { data: this.context.files.map(f => ({ filename: f.filename, status: 'modified', changes: f.diff.split('\n').length, patch: f.diff })) } } } } } } /** * Create a mock Probot context */ createMockProbotContext(): any { const octokit = this.createMockOctokit() return { log: { info: (msg: string) => this.log(`â„šī¸ ${msg}`), warn: (msg: string) => this.log(`âš ī¸ ${msg}`), error: (msg: string) => this.log(`❌ ${msg}`), debug: (msg: string) => this.log(`🐛 ${msg}`) }, octokit, payload: { pull_request: this.context, repository: { owner: {login: 'mock-owner'}, name: 'mock-repo' } }, repo: () => ({ owner: 'mock-owner', repo: 'mock-repo' }) } } private recordCall(method: string, endpoint: string, payload: any): void { this.calls.push({ method, endpoint, payload, timestamp: Date.now() }) } private log(message: string): void { const timestamp = new Date().toISOString() const logLine = `[${timestamp}] ${message}` console.log(logLine) if (this.logFile) { fs.appendFileSync(this.logFile, logLine + '\n') } } getCalls(): MockedCall[] { return [...this.calls] } getComments(): ReviewComment[] { return [...this.comments] } getCreatedPRs(): any[] { return [...this.createdPRs] } clear(): void { this.calls = [] this.comments = [] this.createdPRs = [] } generateReport(): string { const lines = [ '=== Mock GitHub Activity Report ===', `Total API Calls: ${this.calls.length}`, `Comments Created: ${this.comments.length}`, `PRs Created: ${this.createdPRs.length}`, '', 'API Calls:', ...this.calls.map(c => ` ${c.method} ${c.endpoint}`), '', 'Comments:', ...this.comments.map( c => ` ${c.path}:${c.line} - ${c.body.substring(0, 50)}...` ), '', 'Created PRs:', ...this.createdPRs.map(pr => ` #${pr.number}: ${pr.title}`) ] return lines.join('\n') } } /** * Utility to suppress actual GitHub calls during testing */ export function suppressGitHubCalls(): void { // Set environment variable to disable actual GitHub integration process.env.ENABLE_AUTO_PR = 'false' // Disable auto-PR in eval mode by default } /** * Restore GitHub calls (clear eval mode) */ export function restoreGitHubCalls(): void { // No eval mode to clear since PRIX_EVAL_MODE is not allowed }