Files
coder/enterprise/cli/proxyserver_test.go
T
Mathias Fredriksson ed4311b2cb ci: add Git usr/bin to PATH on Windows (#25939)
## Summary

Fixes all 9 Windows CI test failures caused by the mise CI refactor
(`fe257666d7`, PR #25727).

### Root cause

`jdx/mise-action` exports `Path` (Windows convention) via `GITHUB_ENV`.
Bash on Windows maintains its own `PATH`. When Go's `os.Environ()`
returns both, `cmd.exe` subprocesses non-deterministically pick the
MSYS-translated `PATH` (forward slashes), causing Windows executables
(`printf`, `powershell.exe`, `cmd.exe`) to be unresolvable.

These failures only appeared on `main` (where `-count=1` forces real
test execution) and were masked on PRs by Go test cache.

### Fixes applied

**CI (`setup-mise` action)**:
- Write both `Path` and `PATH` to `GITHUB_ENV` with Git usr/bin
prepended

**Code (`cli/root.go`)**:
- Add `appendAndDedupEnv` helper that deduplicates case-insensitive env
vars on Windows, preferring native Windows paths (backslashes) over MSYS
paths

**Code (`cli/configssh_windows.go`)**:
- Use absolute paths for `powershell.exe` and `cmd.exe` in the SSH
config `Match exec` escape function, avoiding PATH resolution entirely

**Tests**:
- Switch `--header-command` tests from `printf` to `echo` (cmd.exe
builtin) for reliable cross-platform execution
- Add env dedup in `Test_sshConfigMatchExecEscape` for subprocess PATH
consistency

Fixes coder/internal#1556, coder/internal#1558, coder/internal#1559

> 🤖 Generated by Coder agent, will be reviewed by @mafredri. 🏂🏻
2026-06-02 11:51:16 +10:00

149 lines
4.6 KiB
Go

package cli_test
import (
"bufio"
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"sync"
"sync/atomic"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/pty/ptytest"
"github.com/coder/coder/v2/testutil"
)
func Test_ProxyServer_Headers(t *testing.T) {
t.Parallel()
const (
headerName1 = "X-Test-Header-1"
headerVal1 = "test-value-1"
headerName2 = "X-Test-Header-2"
headerVal2 = "test-value-2"
)
// We're not going to actually start a proxy, we're going to point it
// towards a fake server that returns an unexpected status code. This'll
// cause the proxy to exit with an error that we can check for.
var called atomic.Int64
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
called.Add(1)
assert.Equal(t, headerVal1, r.Header.Get(headerName1))
assert.Equal(t, headerVal2, r.Header.Get(headerName2))
w.WriteHeader(http.StatusTeapot) // lol
}))
defer srv.Close()
inv, _ := newCLI(t, "wsproxy", "server",
"--primary-access-url", srv.URL,
"--proxy-session-token", "test-token",
"--access-url", "http://localhost:8080",
"--http-address", ":0",
"--header", fmt.Sprintf("%s=%s", headerName1, headerVal1),
"--header-command", fmt.Sprintf("echo %s=%s", headerName2, headerVal2),
)
pty := ptytest.New(t)
inv.Stdout = pty.Output()
err := inv.Run()
require.Error(t, err)
require.ErrorContains(t, err, "unexpected status code 418")
require.NoError(t, pty.Close())
assert.EqualValues(t, 1, called.Load())
}
//nolint:paralleltest,tparallel // Test uses a static port.
func TestWorkspaceProxy_Server_PrometheusEnabled(t *testing.T) {
// Ephemeral ports have a tendency to conflict and fail with `bind: address already in use` error.
// This workaround forces a static port for Prometheus that hopefully won't be used by other tests.
prometheusPort := 32002
var wg sync.WaitGroup
wg.Add(1)
// Start fake coderd
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/v2/workspaceproxies/me/register" {
// Give fake app_security_key (96 bytes)
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`{"app_security_key": "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789123456012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789123456"}`))
return
}
if r.URL.Path == "/api/v2/workspaceproxies/me/coordinate" ||
r.URL.Path == "/api/v2/buildinfo" {
// Slow down proxy registration, so that test runner can check if Prometheus endpoint is exposed.
wg.Wait()
// Does not matter, we are not going to implement a real workspace proxy.
w.WriteHeader(http.StatusNotImplemented)
return
}
w.Header().Add("Content-Type", "application/json")
_, _ = w.Write([]byte(`{}`)) // build info can be ignored
}))
defer srv.Close()
defer wg.Done()
// Configure CLI client
inv, _ := newCLI(t, "wsproxy", "server",
"--primary-access-url", srv.URL,
"--proxy-session-token", "test-token",
"--access-url", "http://foobar:3001",
"--http-address", ":0",
"--prometheus-enable",
"--prometheus-address", fmt.Sprintf("127.0.0.1:%d", prometheusPort),
)
pty := ptytest.New(t).Attach(inv)
ctx, cancel := context.WithTimeout(inv.Context(), testutil.WaitLong)
defer cancel()
// Start "wsproxy server" command
clitest.StartWithAssert(t, inv, func(t *testing.T, err error) {
// actually no assertions are needed as the test verifies only Prometheus endpoint
})
pty.ExpectMatchContext(ctx, "Started HTTP listener at")
// Fetch metrics from Prometheus endpoint
var res *http.Response
client := &http.Client{}
require.Eventually(t, func() bool {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("http://127.0.0.1:%d", prometheusPort), nil)
assert.NoError(t, err)
// nolint:bodyclose
res, err = client.Do(req)
return err == nil
}, testutil.WaitShort, testutil.IntervalFast)
defer res.Body.Close()
// Scan for metric patterns
scanner := bufio.NewScanner(res.Body)
hasGoStats := false
hasPromHTTP := false
for scanner.Scan() {
if strings.HasPrefix(scanner.Text(), "go_goroutines") {
hasGoStats = true
continue
}
if strings.HasPrefix(scanner.Text(), "promhttp_metric_handler_requests_total") {
hasPromHTTP = true
continue
}
t.Logf("scanned %s", scanner.Text())
}
require.NoError(t, scanner.Err())
// Verify patterns
require.True(t, hasGoStats, "Go stats are missing")
require.True(t, hasPromHTTP, "Prometheus HTTP metrics are missing")
}