From 1c7ad118b7934271e8707c253c14edf82d656ad5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 06:19:00 +0000 Subject: [PATCH 1/7] Initial plan From d2bf5937fef06f64dfd3aa14abb18ad6e0737676 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 06:30:03 +0000 Subject: [PATCH 2/7] Add integration test suite with GitHub Actions workflow Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/integration.yml | 60 ++++++++ .gitignore | 3 + DEV.md | 15 ++ integration/README.md | 76 ++++++++++ integration/go.mod | 11 ++ integration/integration_test.go | 221 ++++++++++++++++++++++++++++++ 6 files changed, 386 insertions(+) create mode 100644 .github/workflows/integration.yml create mode 100644 integration/README.md create mode 100644 integration/go.mod create mode 100644 integration/integration_test.go diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 00000000..3bd344f0 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,60 @@ +name: "Integration Tests" + +on: + push: + branches: + - 'main' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integration: + runs-on: ubuntu-latest + env: + GOPROXY: https://proxy.golang.org/,direct + GOPRIVATE: "" + GONOPROXY: "" + GONOSUMDB: github.com/github/* + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: ">=1.22" + check-latest: true + + - name: Build gh-models binary + run: script/build + + - name: Run integration tests (without auth) + working-directory: integration + run: | + go mod tidy + go test -v -timeout=5m + env: + # Explicitly unset any GitHub tokens to test unauthenticated behavior + GITHUB_TOKEN: "" + GH_TOKEN: "" + + - name: Install gh CLI + if: github.event_name == 'workflow_dispatch' + run: | + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt update + sudo apt install gh + + - name: Run integration tests (with auth) + if: github.event_name == 'workflow_dispatch' + working-directory: integration + run: | + gh auth login --with-token <<< "${{ secrets.GITHUB_TOKEN }}" + go test -v -timeout=10m + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 54f9c6bc..396d8827 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ /gh-models-linux-* /gh-models-windows-* /gh-models-android-* + +# Integration test dependencies +integration/go.sum diff --git a/DEV.md b/DEV.md index 36c44fd1..f0cc739d 100644 --- a/DEV.md +++ b/DEV.md @@ -34,6 +34,21 @@ make vet # to find suspicious constructs make tidy # to keep dependencies up-to-date ``` +### Integration Tests + +In addition to unit tests, we have integration tests that use the compiled binary to test against live endpoints: + +```shell +# Build the binary first +script/build + +# Run integration tests +cd integration +go test -v +``` + +Integration tests are located in the `integration/` directory and automatically skip tests requiring authentication when no GitHub token is available. See `integration/README.md` for more details. + ## Releasing When upgrading or installing the extension using `gh extension upgrade github/gh-models` or diff --git a/integration/README.md b/integration/README.md new file mode 100644 index 00000000..5ebeb9a5 --- /dev/null +++ b/integration/README.md @@ -0,0 +1,76 @@ +# Integration Tests + +This directory contains integration tests for the `gh-models` CLI extension. These tests are separate from the unit tests and use the compiled binary to test actual functionality. + +## Overview + +The integration tests: +- Use the compiled `gh-models` binary (not mocked clients) +- Test basic functionality of each command (`list`, `run`, `view`, `eval`) +- Are designed to work with or without GitHub authentication +- Skip tests requiring live endpoints when authentication is unavailable +- Keep assertions minimal to avoid brittleness + +## Running the Tests + +### Prerequisites + +1. Build the `gh-models` binary: + ```bash + cd .. + script/build + ``` + +2. (Optional) Authenticate with GitHub CLI for full testing: + ```bash + gh auth login + ``` + +### Running Locally + +From the integration directory: +```bash +go test -v +``` + +Without authentication, some tests will be skipped: +``` +=== RUN TestIntegrationHelp +--- PASS: TestIntegrationHelp (0.05s) +=== RUN TestIntegrationList + integration_test.go:90: Skipping integration test - no GitHub authentication available +--- SKIP: TestIntegrationList (0.04s) +``` + +With authentication, all tests should run and test live endpoints. + +## CI/CD + +The integration tests run automatically on pushes to `main` via the GitHub Actions workflow `.github/workflows/integration.yml`. + +The workflow: +1. Builds the binary +2. Runs tests without authentication (tests basic functionality) +3. On manual dispatch, can also run with authentication for full testing + +## Test Structure + +Each test follows this pattern: +- Check for binary existence (skip if not built) +- Check for authentication (skip live endpoint tests if unavailable) +- Execute the binary with specific arguments +- Verify basic output format and success/failure + +Tests are intentionally simple and focus on: +- Commands execute without errors +- Help text is present and correctly formatted +- Basic output format is as expected +- Authentication requirements are respected + +## Adding New Tests + +When adding new commands or features: +1. Add a corresponding integration test +2. Follow the existing pattern of checking authentication +3. Keep assertions minimal but meaningful +4. Ensure tests work both with and without authentication \ No newline at end of file diff --git a/integration/go.mod b/integration/go.mod new file mode 100644 index 00000000..3e104b8f --- /dev/null +++ b/integration/go.mod @@ -0,0 +1,11 @@ +module github.com/github/gh-models/integration + +go 1.22 + +require github.com/stretchr/testify v1.10.0 + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/integration/integration_test.go b/integration/integration_test.go new file mode 100644 index 00000000..3de61e07 --- /dev/null +++ b/integration/integration_test.go @@ -0,0 +1,221 @@ +package integration + +import ( + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +const ( + binaryName = "gh-models" + timeoutDuration = 30 * time.Second +) + +// getBinaryPath returns the path to the compiled gh-models binary +func getBinaryPath(t *testing.T) string { + wd, err := os.Getwd() + require.NoError(t, err) + + // Binary should be in the parent directory + binaryPath := filepath.Join(filepath.Dir(wd), binaryName) + + // Check if binary exists + if _, err := os.Stat(binaryPath); os.IsNotExist(err) { + t.Skipf("Binary %s not found. Run 'script/build' first.", binaryPath) + } + + return binaryPath +} + +// hasAuthToken checks if GitHub authentication is available +func hasAuthToken() bool { + // Check if gh CLI is available and authenticated + cmd := exec.Command("gh", "auth", "status") + return cmd.Run() == nil +} + +// runCommand executes the gh-models binary with given arguments +func runCommand(t *testing.T, args ...string) (stdout, stderr string, err error) { + binaryPath := getBinaryPath(t) + + cmd := exec.Command(binaryPath, args...) + cmd.Env = os.Environ() + + // Set timeout + done := make(chan error, 1) + var stdoutBytes, stderrBytes []byte + + go func() { + stdoutBytes, err = cmd.Output() + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + stderrBytes = exitError.Stderr + } + } + done <- err + }() + + select { + case err = <-done: + return string(stdoutBytes), string(stderrBytes), err + case <-time.After(timeoutDuration): + if cmd.Process != nil { + cmd.Process.Kill() + } + t.Fatalf("Command timed out after %v", timeoutDuration) + return "", "", nil + } +} + +func TestIntegrationHelp(t *testing.T) { + stdout, stderr, err := runCommand(t, "--help") + + // Help should always work, even without auth + require.NoError(t, err, "stderr: %s", stderr) + require.Contains(t, stdout, "GitHub Models CLI extension") + require.Contains(t, stdout, "Available Commands:") + require.Contains(t, stdout, "list") + require.Contains(t, stdout, "run") + require.Contains(t, stdout, "view") + require.Contains(t, stdout, "eval") +} + +func TestIntegrationList(t *testing.T) { + if !hasAuthToken() { + t.Skip("Skipping integration test - no GitHub authentication available") + } + + stdout, stderr, err := runCommand(t, "list") + + if err != nil { + t.Logf("List command failed. stdout: %s, stderr: %s", stdout, stderr) + // If the command fails due to auth issues, skip the test + if strings.Contains(stderr, "authentication") || strings.Contains(stderr, "token") { + t.Skip("Skipping - authentication issue") + } + require.NoError(t, err, "List command should succeed with valid auth") + } + + // Basic verification that list command produces expected output format + require.NotEmpty(t, stdout, "List should produce output") + // Should contain some indication of models or table headers + lowerOut := strings.ToLower(stdout) + hasExpectedContent := strings.Contains(lowerOut, "model") || + strings.Contains(lowerOut, "name") || + strings.Contains(lowerOut, "id") || + strings.Contains(lowerOut, "display") + require.True(t, hasExpectedContent, "List output should contain model information") +} + +func TestIntegrationListHelp(t *testing.T) { + stdout, stderr, err := runCommand(t, "list", "--help") + + require.NoError(t, err, "stderr: %s", stderr) + require.Contains(t, stdout, "Returns a list of models") + require.Contains(t, stdout, "Usage:") +} + +func TestIntegrationView(t *testing.T) { + if !hasAuthToken() { + t.Skip("Skipping integration test - no GitHub authentication available") + } + + // First get a model to view + listOut, _, listErr := runCommand(t, "list") + if listErr != nil { + t.Skip("Cannot run view test - list command failed") + } + + // Extract a model name from list output (this is basic parsing) + lines := strings.Split(listOut, "\n") + var modelName string + for _, line := range lines { + line = strings.TrimSpace(line) + // Look for lines that might contain model IDs (containing forward slash) + if strings.Contains(line, "/") && !strings.HasPrefix(line, "Usage:") && + !strings.HasPrefix(line, "gh models") && line != "" { + // Try to extract what looks like a model ID + fields := strings.Fields(line) + for _, field := range fields { + if strings.Contains(field, "/") { + modelName = field + break + } + } + if modelName != "" { + break + } + } + } + + if modelName == "" { + t.Skip("Could not extract model name from list output") + } + + stdout, stderr, err := runCommand(t, "view", modelName) + + if err != nil { + t.Logf("View command failed for model %s. stdout: %s, stderr: %s", modelName, stdout, stderr) + // If the command fails due to auth issues, skip the test + if strings.Contains(stderr, "authentication") || strings.Contains(stderr, "token") { + t.Skip("Skipping - authentication issue") + } + require.NoError(t, err, "View command should succeed with valid model") + } + + // Basic verification that view command produces expected output + require.NotEmpty(t, stdout, "View should produce output") + lowerOut := strings.ToLower(stdout) + hasExpectedContent := strings.Contains(lowerOut, "model") || + strings.Contains(lowerOut, "name") || + strings.Contains(lowerOut, "description") || + strings.Contains(lowerOut, "publisher") + require.True(t, hasExpectedContent, "View output should contain model details") +} + +func TestIntegrationViewHelp(t *testing.T) { + stdout, stderr, err := runCommand(t, "view", "--help") + + require.NoError(t, err, "stderr: %s", stderr) + require.Contains(t, stdout, "Returns details about the specified model") + require.Contains(t, stdout, "Usage:") +} + +func TestIntegrationRunHelp(t *testing.T) { + stdout, stderr, err := runCommand(t, "run", "--help") + + require.NoError(t, err, "stderr: %s", stderr) + require.Contains(t, stdout, "Prompts the specified model") + require.Contains(t, stdout, "Usage:") +} + +func TestIntegrationEvalHelp(t *testing.T) { + stdout, stderr, err := runCommand(t, "eval", "--help") + + require.NoError(t, err, "stderr: %s", stderr) + require.Contains(t, stdout, "Runs evaluation tests against a model") + require.Contains(t, stdout, "Usage:") +} + +// TestIntegrationRun tests the run command with a simple prompt +// This test is more limited since it requires actual model inference +func TestIntegrationRun(t *testing.T) { + if !hasAuthToken() { + t.Skip("Skipping integration test - no GitHub authentication available") + } + + // We'll test with a very simple prompt to minimize cost and time + // Using a basic model and short prompt + stdout, _, err := runCommand(t, "run", "--help") + require.NoError(t, err, "Run help should work") + + // For now, just verify the help works. + // A full test would require setting up a prompt and model, + // which might be expensive for CI + require.Contains(t, stdout, "Prompts the specified model") +} From 78b652d9bf43ffc4a9697d0cc6a31f08bfbb5e28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 08:23:49 +0000 Subject: [PATCH 3/7] Address feedback: add build target, update workflow, and simplify Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/integration.yml | 22 +++------------------- DEV.md | 2 +- Makefile | 5 +++++ 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 3bd344f0..75c7b75d 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -8,6 +8,7 @@ on: permissions: contents: read + models: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -30,7 +31,7 @@ jobs: check-latest: true - name: Build gh-models binary - run: script/build + run: make build - name: Run integration tests (without auth) working-directory: integration @@ -40,21 +41,4 @@ jobs: env: # Explicitly unset any GitHub tokens to test unauthenticated behavior GITHUB_TOKEN: "" - GH_TOKEN: "" - - - name: Install gh CLI - if: github.event_name == 'workflow_dispatch' - run: | - curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt update - sudo apt install gh - - - name: Run integration tests (with auth) - if: github.event_name == 'workflow_dispatch' - working-directory: integration - run: | - gh auth login --with-token <<< "${{ secrets.GITHUB_TOKEN }}" - go test -v -timeout=10m - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GH_TOKEN: "" \ No newline at end of file diff --git a/DEV.md b/DEV.md index f0cc739d..fb33d9bc 100644 --- a/DEV.md +++ b/DEV.md @@ -40,7 +40,7 @@ In addition to unit tests, we have integration tests that use the compiled binar ```shell # Build the binary first -script/build +make build # Run integration tests cd integration diff --git a/Makefile b/Makefile index 898120db..c8d50d01 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,11 @@ check: fmt vet tidy test .PHONY: check +build: + @echo "==> building gh-models binary <==" + script/build +.PHONY: build + fmt: @echo "==> running Go format <==" gofmt -s -l -w . From 296a540f52d2a608654da01a8f11a1a9c2afd36c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:52:51 +0000 Subject: [PATCH 4/7] Add org tests, pass GitHub token in workflow, and add integration Makefile target Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/integration.yml | 6 ++---- Makefile | 5 +++++ integration/integration_test.go | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 75c7b75d..7d9477f4 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -33,12 +33,10 @@ jobs: - name: Build gh-models binary run: make build - - name: Run integration tests (without auth) + - name: Run integration tests working-directory: integration run: | go mod tidy go test -v -timeout=5m env: - # Explicitly unset any GitHub tokens to test unauthenticated behavior - GITHUB_TOKEN: "" - GH_TOKEN: "" \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/Makefile b/Makefile index c8d50d01..7194239c 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,11 @@ build: script/build .PHONY: build +integration: build + @echo "==> running integration tests <==" + cd integration && go test -v -timeout=5m +.PHONY: integration + fmt: @echo "==> running Go format <==" gofmt -s -l -w . diff --git a/integration/integration_test.go b/integration/integration_test.go index 3de61e07..0e9704a2 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -219,3 +219,29 @@ func TestIntegrationRun(t *testing.T) { // which might be expensive for CI require.Contains(t, stdout, "Prompts the specified model") } + +// TestIntegrationRunWithOrg tests the run command with --org flag +func TestIntegrationRunWithOrg(t *testing.T) { + if !hasAuthToken() { + t.Skip("Skipping integration test - no GitHub authentication available") + } + + // Test run command with --org flag (using help to avoid expensive API calls) + stdout, _, err := runCommand(t, "run", "--org", "test-org", "--help") + require.NoError(t, err, "Run help with --org should work") + require.Contains(t, stdout, "Prompts the specified model") + require.Contains(t, stdout, "--org string") +} + +// TestIntegrationEvalWithOrg tests the eval command with --org flag +func TestIntegrationEvalWithOrg(t *testing.T) { + if !hasAuthToken() { + t.Skip("Skipping integration test - no GitHub authentication available") + } + + // Test eval command with --org flag (using help to avoid expensive API calls) + stdout, _, err := runCommand(t, "eval", "--org", "test-org", "--help") + require.NoError(t, err, "Eval help with --org should work") + require.Contains(t, stdout, "Runs evaluation tests against a model") + require.Contains(t, stdout, "--org string") +} From 175460997f13fb2b40eb23e40f7e2755e2606207 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:49:15 +0000 Subject: [PATCH 5/7] Change integration test binary name to gh-models-test to avoid confusion Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .gitignore | 1 + Makefile | 2 ++ integration/integration_test.go | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 396d8827..8f1fad44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /gh-models /gh-models.exe +/gh-models-test /gh-models-darwin-* /gh-models-linux-* /gh-models-windows-* diff --git a/Makefile b/Makefile index 7194239c..ab83ff62 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,8 @@ build: .PHONY: build integration: build + @echo "==> building gh-models-test binary for integration tests <==" + go build -o gh-models-test main.go @echo "==> running integration tests <==" cd integration && go test -v -timeout=5m .PHONY: integration diff --git a/integration/integration_test.go b/integration/integration_test.go index 0e9704a2..5fe67103 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -12,7 +12,7 @@ import ( ) const ( - binaryName = "gh-models" + binaryName = "gh-models-test" timeoutDuration = 30 * time.Second ) From 5614b048ffbf92ad1341b256eb18f61a3d0cd2e9 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Sun, 3 Aug 2025 21:12:44 +0000 Subject: [PATCH 6/7] Refactor integration tests: remove unused auth token check and streamline test cases --- Makefile | 4 +- integration/integration_test.go | 168 +++----------------------------- 2 files changed, 12 insertions(+), 160 deletions(-) diff --git a/Makefile b/Makefile index ab83ff62..12bde1ff 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,8 @@ build: .PHONY: build integration: build - @echo "==> building gh-models-test binary for integration tests <==" - go build -o gh-models-test main.go @echo "==> running integration tests <==" - cd integration && go test -v -timeout=5m + cd integration && go mod tidy && go test -v -timeout=5m .PHONY: integration fmt: diff --git a/integration/integration_test.go b/integration/integration_test.go index 5fe67103..0b7bfb28 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -32,13 +32,6 @@ func getBinaryPath(t *testing.T) string { return binaryPath } -// hasAuthToken checks if GitHub authentication is available -func hasAuthToken() bool { - // Check if gh CLI is available and authenticated - cmd := exec.Command("gh", "auth", "status") - return cmd.Run() == nil -} - // runCommand executes the gh-models binary with given arguments func runCommand(t *testing.T, args ...string) (stdout, stderr string, err error) { binaryPath := getBinaryPath(t) @@ -72,26 +65,8 @@ func runCommand(t *testing.T, args ...string) (stdout, stderr string, err error) } } -func TestIntegrationHelp(t *testing.T) { - stdout, stderr, err := runCommand(t, "--help") - - // Help should always work, even without auth - require.NoError(t, err, "stderr: %s", stderr) - require.Contains(t, stdout, "GitHub Models CLI extension") - require.Contains(t, stdout, "Available Commands:") - require.Contains(t, stdout, "list") - require.Contains(t, stdout, "run") - require.Contains(t, stdout, "view") - require.Contains(t, stdout, "eval") -} - -func TestIntegrationList(t *testing.T) { - if !hasAuthToken() { - t.Skip("Skipping integration test - no GitHub authentication available") - } - +func TestList(t *testing.T) { stdout, stderr, err := runCommand(t, "list") - if err != nil { t.Logf("List command failed. stdout: %s, stderr: %s", stdout, stderr) // If the command fails due to auth issues, skip the test @@ -105,143 +80,22 @@ func TestIntegrationList(t *testing.T) { require.NotEmpty(t, stdout, "List should produce output") // Should contain some indication of models or table headers lowerOut := strings.ToLower(stdout) - hasExpectedContent := strings.Contains(lowerOut, "model") || - strings.Contains(lowerOut, "name") || - strings.Contains(lowerOut, "id") || - strings.Contains(lowerOut, "display") + hasExpectedContent := strings.Contains(lowerOut, "openai/gpt-4.1") require.True(t, hasExpectedContent, "List output should contain model information") } -func TestIntegrationListHelp(t *testing.T) { - stdout, stderr, err := runCommand(t, "list", "--help") - - require.NoError(t, err, "stderr: %s", stderr) - require.Contains(t, stdout, "Returns a list of models") - require.Contains(t, stdout, "Usage:") -} - -func TestIntegrationView(t *testing.T) { - if !hasAuthToken() { - t.Skip("Skipping integration test - no GitHub authentication available") - } - - // First get a model to view - listOut, _, listErr := runCommand(t, "list") - if listErr != nil { - t.Skip("Cannot run view test - list command failed") - } - - // Extract a model name from list output (this is basic parsing) - lines := strings.Split(listOut, "\n") - var modelName string - for _, line := range lines { - line = strings.TrimSpace(line) - // Look for lines that might contain model IDs (containing forward slash) - if strings.Contains(line, "/") && !strings.HasPrefix(line, "Usage:") && - !strings.HasPrefix(line, "gh models") && line != "" { - // Try to extract what looks like a model ID - fields := strings.Fields(line) - for _, field := range fields { - if strings.Contains(field, "/") { - modelName = field - break - } - } - if modelName != "" { - break - } - } - } - - if modelName == "" { - t.Skip("Could not extract model name from list output") - } - - stdout, stderr, err := runCommand(t, "view", modelName) - - if err != nil { - t.Logf("View command failed for model %s. stdout: %s, stderr: %s", modelName, stdout, stderr) - // If the command fails due to auth issues, skip the test - if strings.Contains(stderr, "authentication") || strings.Contains(stderr, "token") { - t.Skip("Skipping - authentication issue") - } - require.NoError(t, err, "View command should succeed with valid model") - } - - // Basic verification that view command produces expected output - require.NotEmpty(t, stdout, "View should produce output") - lowerOut := strings.ToLower(stdout) - hasExpectedContent := strings.Contains(lowerOut, "model") || - strings.Contains(lowerOut, "name") || - strings.Contains(lowerOut, "description") || - strings.Contains(lowerOut, "publisher") - require.True(t, hasExpectedContent, "View output should contain model details") -} - -func TestIntegrationViewHelp(t *testing.T) { - stdout, stderr, err := runCommand(t, "view", "--help") - - require.NoError(t, err, "stderr: %s", stderr) - require.Contains(t, stdout, "Returns details about the specified model") - require.Contains(t, stdout, "Usage:") -} - -func TestIntegrationRunHelp(t *testing.T) { - stdout, stderr, err := runCommand(t, "run", "--help") - - require.NoError(t, err, "stderr: %s", stderr) - require.Contains(t, stdout, "Prompts the specified model") - require.Contains(t, stdout, "Usage:") -} - -func TestIntegrationEvalHelp(t *testing.T) { - stdout, stderr, err := runCommand(t, "eval", "--help") - - require.NoError(t, err, "stderr: %s", stderr) - require.Contains(t, stdout, "Runs evaluation tests against a model") - require.Contains(t, stdout, "Usage:") -} - -// TestIntegrationRun tests the run command with a simple prompt +// TestRun tests the run command with a simple prompt // This test is more limited since it requires actual model inference -func TestIntegrationRun(t *testing.T) { - if !hasAuthToken() { - t.Skip("Skipping integration test - no GitHub authentication available") - } - - // We'll test with a very simple prompt to minimize cost and time - // Using a basic model and short prompt - stdout, _, err := runCommand(t, "run", "--help") - require.NoError(t, err, "Run help should work") - - // For now, just verify the help works. - // A full test would require setting up a prompt and model, - // which might be expensive for CI - require.Contains(t, stdout, "Prompts the specified model") +func TestRun(t *testing.T) { + stdout, _, err := runCommand(t, "run", "openai/gpt-4.1-nano", "say 'pain' in french") + require.NoError(t, err, "Run should work") + require.Contains(t, strings.ToLower(stdout), "pain") } // TestIntegrationRunWithOrg tests the run command with --org flag -func TestIntegrationRunWithOrg(t *testing.T) { - if !hasAuthToken() { - t.Skip("Skipping integration test - no GitHub authentication available") - } - +func TestRunWithOrg(t *testing.T) { // Test run command with --org flag (using help to avoid expensive API calls) - stdout, _, err := runCommand(t, "run", "--org", "test-org", "--help") - require.NoError(t, err, "Run help with --org should work") - require.Contains(t, stdout, "Prompts the specified model") - require.Contains(t, stdout, "--org string") -} - -// TestIntegrationEvalWithOrg tests the eval command with --org flag -func TestIntegrationEvalWithOrg(t *testing.T) { - if !hasAuthToken() { - t.Skip("Skipping integration test - no GitHub authentication available") - } - - // Test eval command with --org flag (using help to avoid expensive API calls) - stdout, _, err := runCommand(t, "eval", "--org", "test-org", "--help") - require.NoError(t, err, "Eval help with --org should work") - require.Contains(t, stdout, "Runs evaluation tests against a model") - require.Contains(t, stdout, "--org string") + stdout, _, err := runCommand(t, "run", "openai/gpt-4.1-nano", "say 'pain' in french", "--org", "github") + require.NoError(t, err, "Run should work") + require.Contains(t, strings.ToLower(stdout), "pain") } From 92529c593c681cf2e1b21aa998132cfc7801c0f6 Mon Sep 17 00:00:00 2001 From: Peli de Halleux Date: Sun, 3 Aug 2025 21:14:31 +0000 Subject: [PATCH 7/7] Refactor integration workflow: streamline Go setup and remove unnecessary build steps --- .github/workflows/integration.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 7d9477f4..44376043 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -24,19 +24,11 @@ jobs: GONOSUMDB: github.com/github/* steps: - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 + - name: Setup Go + uses: actions/setup-go@v5 with: - go-version: ">=1.22" - check-latest: true - - - name: Build gh-models binary - run: make build - + go-version-file: 'go.mod' - name: Run integration tests - working-directory: integration - run: | - go mod tidy - go test -v -timeout=5m + run: make integration env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file