From fd73d0264cd240d895c114657b06fd1b952b97b6 Mon Sep 17 00:00:00 2001 From: Sean Goedecke Date: Thu, 27 Nov 2025 20:59:41 +0000 Subject: [PATCH] Mock inference in CI --- .github/workflows/ci.yml | 75 ++++++++++++++++++++++++++++++-- script/mock-inference-server.mjs | 71 ++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 script/mock-inference-server.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a2e703..babbaab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,20 +56,51 @@ jobs: id: checkout uses: actions/checkout@v5 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .node-version + + - name: Start Mock Inference Server + id: mock-server + run: | + node script/mock-inference-server.mjs & + echo "pid=$!" >> $GITHUB_OUTPUT + # Wait for server to be ready + for i in {1..10}; do + if curl -s http://localhost:3456/health > /dev/null; then + echo "Mock server is ready" + break + fi + sleep 1 + done + - name: Test Local Action id: test-action - continue-on-error: true uses: ./ with: prompt: hello + endpoint: http://localhost:3456 env: GITHUB_TOKEN: ${{ github.token }} - name: Print Output id: output - continue-on-error: true run: echo "${{ steps.test-action.outputs.response }}" + - name: Verify Output + run: | + response="${{ steps.test-action.outputs.response }}" + if [[ -z "$response" ]]; then + echo "Error: No response received" + exit 1 + fi + echo "Response received: $response" + + - name: Stop Mock Server + if: always() + run: kill ${{ steps.mock-server.outputs.pid }} || true + test-action-prompt-file: name: GitHub Actions Test with Prompt File runs-on: ubuntu-latest @@ -79,6 +110,25 @@ jobs: id: checkout uses: actions/checkout@v5 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: .node-version + + - name: Start Mock Inference Server + id: mock-server + run: | + node script/mock-inference-server.mjs & + echo "pid=$!" >> $GITHUB_OUTPUT + # Wait for server to be ready + for i in {1..10}; do + if curl -s http://localhost:3456/health > /dev/null; then + echo "Mock server is ready" + break + fi + sleep 1 + done + - name: Create Prompt File run: echo "hello" > prompt.txt @@ -87,16 +137,33 @@ jobs: - name: Test Local Action with Prompt File id: test-action-prompt-file - continue-on-error: true uses: ./ with: prompt-file: prompt.txt system-prompt-file: system-prompt.txt + endpoint: http://localhost:3456 env: GITHUB_TOKEN: ${{ github.token }} - name: Print Output - continue-on-error: true run: | echo "Response saved to: ${{ steps.test-action-prompt-file.outputs.response-file }}" cat "${{ steps.test-action-prompt-file.outputs.response-file }}" + + - name: Verify Output + run: | + response_file="${{ steps.test-action-prompt-file.outputs.response-file }}" + if [[ ! -f "$response_file" ]]; then + echo "Error: Response file not found" + exit 1 + fi + content=$(cat "$response_file") + if [[ -z "$content" ]]; then + echo "Error: Response file is empty" + exit 1 + fi + echo "Response file content: $content" + + - name: Stop Mock Server + if: always() + run: kill ${{ steps.mock-server.outputs.pid }} || true diff --git a/script/mock-inference-server.mjs b/script/mock-inference-server.mjs new file mode 100644 index 0000000..6eaf031 --- /dev/null +++ b/script/mock-inference-server.mjs @@ -0,0 +1,71 @@ +#!/usr/bin/env node +/** + * A simple mock OpenAI-compatible inference server for CI testing. + * This returns predictable responses without needing real API credentials. + */ + +import http from 'http' + +const PORT = process.env.MOCK_SERVER_PORT || 3456 + +const server = http.createServer((req, res) => { + let body = '' + + req.on('data', chunk => { + body += chunk.toString() + }) + + req.on('end', () => { + console.log(`[Mock Server] ${req.method} ${req.url}`) + + // Handle chat completions endpoint + if (req.url === '/chat/completions' && req.method === 'POST') { + const request = JSON.parse(body) + const userMessage = request.messages?.find(m => m.role === 'user')?.content || 'No prompt' + + const response = { + id: 'mock-completion-id', + object: 'chat.completion', + created: Date.now(), + model: request.model || 'mock-model', + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: `Mock response to: "${userMessage.slice(0, 50)}..."`, + }, + finish_reason: 'stop', + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 20, + total_tokens: 30, + }, + } + + res.writeHead(200, {'Content-Type': 'application/json'}) + res.end(JSON.stringify(response)) + return + } + + // Health check endpoint + if (req.url === '/health' || req.url === '/') { + res.writeHead(200, {'Content-Type': 'application/json'}) + res.end(JSON.stringify({status: 'ok'})) + return + } + + // 404 for unknown routes + res.writeHead(404, {'Content-Type': 'application/json'}) + res.end(JSON.stringify({error: 'Not found'})) + }) +}) + +server.listen(PORT, () => { + console.log(`[Mock Server] Listening on http://localhost:${PORT}`) + console.log('[Mock Server] Endpoints:') + console.log(' POST /chat/completions - Mock chat completion') + console.log(' GET /health - Health check') +})