Files
multica/server/pkg/agent/version_test.go
Bohan Jiang 178cfb5008 fix(daemon): strip Windows chcp noise from runtime version (#2516) (#2521)
The gemini CLI's Windows shim emits `Active code page: 65001` (from
`chcp`) to stdout before the real version reaches `--version` output.
The daemon stored the raw concatenation as the runtime version, so the
runtime detail page rendered `Active code page: 65001 0.42.0` instead
of `0.42.0`.

Scan `<cli> --version` line by line and return the first line carrying
a semver-shaped token. Full strings like `2.1.5 (Claude Code)` or
`codex-cli 0.118.0` survive unchanged; unparseable output falls back to
the trimmed raw value.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-13 16:58:14 +08:00

166 lines
4.3 KiB
Go

package agent
import (
"errors"
"testing"
)
func TestParseSemver(t *testing.T) {
tests := []struct {
input string
want semver
wantErr bool
}{
{"2.0.0", semver{2, 0, 0}, false},
{"v2.1.100", semver{2, 1, 100}, false},
{"2.1.100 (Claude Code)", semver{2, 1, 100}, false},
{"codex-cli 0.118.0", semver{0, 118, 0}, false},
{"1.0.20", semver{1, 0, 20}, false},
{"invalid", semver{}, true},
{"", semver{}, true},
}
for _, tt := range tests {
got, err := parseSemver(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("parseSemver(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
continue
}
if got != tt.want {
t.Errorf("parseSemver(%q) = %v, want %v", tt.input, got, tt.want)
}
}
}
func TestSemverLessThan(t *testing.T) {
tests := []struct {
a, b semver
want bool
}{
{semver{1, 0, 0}, semver{2, 0, 0}, true},
{semver{2, 0, 0}, semver{1, 0, 0}, false},
{semver{2, 0, 0}, semver{2, 1, 0}, true},
{semver{2, 1, 0}, semver{2, 0, 0}, false},
{semver{2, 1, 12}, semver{2, 1, 13}, true},
{semver{2, 1, 13}, semver{2, 1, 12}, false},
{semver{2, 0, 0}, semver{2, 0, 0}, false},
}
for _, tt := range tests {
got := tt.a.lessThan(tt.b)
if got != tt.want {
t.Errorf("%v.lessThan(%v) = %v, want %v", tt.a, tt.b, got, tt.want)
}
}
}
func TestCheckMinCLIVersion(t *testing.T) {
tests := []struct {
name string
input string
wantErr error
}{
{"tagged release at minimum", "v0.2.20", nil},
{"tagged release above minimum", "0.3.1", nil},
{"tagged release below minimum", "v0.2.15", ErrCLIVersionTooOld},
{"empty string", "", ErrCLIVersionMissing},
{"unparsable", "not-a-version", ErrCLIVersionMissing},
{"git-describe dev build past old tag", "v0.2.15-235-gdaf0e935", nil},
{"git-describe dirty dev build", "v0.2.15-235-gdaf0e935-dirty", nil},
{"git-describe dev build past current tag", "v0.2.20-3-gabc1234", nil},
}
for _, tt := range tests {
err := CheckMinCLIVersion(tt.input)
if tt.wantErr == nil && err != nil {
t.Errorf("%s: CheckMinCLIVersion(%q) = %v, want nil", tt.name, tt.input, err)
}
if tt.wantErr != nil && !errors.Is(err, tt.wantErr) {
t.Errorf("%s: CheckMinCLIVersion(%q) = %v, want %v", tt.name, tt.input, err, tt.wantErr)
}
}
}
func TestExtractVersionLine(t *testing.T) {
tests := []struct {
name string
raw string
want string
}{
{
name: "bare semver",
raw: "0.42.0\n",
want: "0.42.0",
},
{
name: "claude full string preserved",
raw: "2.1.5 (Claude Code)\n",
want: "2.1.5 (Claude Code)",
},
{
name: "codex prefix preserved",
raw: "codex-cli 0.118.0\n",
want: "codex-cli 0.118.0",
},
// Reproduces #2516: gemini's Windows shim emits `chcp` output to stdout
// before the real version. The chcp line has no dotted-number form,
// so the semver scan skips it and picks up "0.42.0" from the next line.
{
name: "windows chcp prefix before version",
raw: "Active code page: 65001\n0.42.0\n",
want: "0.42.0",
},
{
name: "windows chcp prefix CRLF",
raw: "Active code page: 65001\r\n0.42.0\r\n",
want: "0.42.0",
},
{
name: "leading blank lines",
raw: "\n\n 0.42.0\n",
want: "0.42.0",
},
{
name: "non-semver output falls back to trimmed raw",
raw: " some-build-id \n",
want: "some-build-id",
},
{
name: "empty input",
raw: "",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := extractVersionLine(tt.raw); got != tt.want {
t.Errorf("extractVersionLine(%q) = %q, want %q", tt.raw, got, tt.want)
}
})
}
}
func TestCheckMinVersion(t *testing.T) {
tests := []struct {
agentType string
version string
wantErr bool
}{
{"claude", "2.0.0", false},
{"claude", "2.1.100", false},
{"claude", "2.1.100 (Claude Code)", false},
{"claude", "v2.0.0", false},
{"claude", "1.0.128", true},
{"claude", "1.9.99", true},
{"claude", "invalid", true},
{"codex", "codex-cli 0.118.0", false},
{"codex", "codex-cli 0.100.0", false},
{"codex", "codex-cli 0.99.0", true},
{"codex", "codex-cli 0.50.0", true},
{"unknown", "1.0.0", false},
}
for _, tt := range tests {
err := CheckMinVersion(tt.agentType, tt.version)
if (err != nil) != tt.wantErr {
t.Errorf("CheckMinVersion(%q, %q) error = %v, wantErr %v", tt.agentType, tt.version, err, tt.wantErr)
}
}
}