Files
coder/agent/agentssh/appname_internal_test.go
T
Ehab Younes c767cc5198 refactor: normalize workspace agent session metadata (#28124)
Phase 1 of the Scalable Approach for Adding New IDE Session Types RFC,
tracked in #27410.

Gives agent session accounting one canonical vocabulary for app names, so
later work can report a count per app without the agent and the server
disagreeing about what an app is called.

- Adds codersdk/appname.go: NormalizeAppName for storage and lookup, and
  AppFamilyName for the places that need a bounded value, such as a
  Prometheus label or the Connection_Type enum.
- agentssh.Server keeps session counters in a map keyed by normalized name
  instead of three fixed atomics, dropping entries at zero.
- Drops MagicSessionType. Sessions carry plain app names, and unrecognized
  ones flow through as themselves rather than collapsing to unknown.

Wire behavior is unchanged: the agent still reports the four session_count_*
fields over Agent API 2.10.
2026-08-21 02:22:18 +03:00

89 lines
2.1 KiB
Go

package agentssh
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
)
func TestExtractAppName(t *testing.T) {
t.Parallel()
envWith := func(value string) []string {
return []string{
"FOO=bar",
fmt.Sprintf("%s=%s", AppNameEnvironmentVariable, value),
"BAZ=qux",
}
}
for _, tc := range []struct {
name string
env []string
want string
}{
{"NoEnvDefaultsToSSH", []string{"FOO=bar"}, "ssh"},
{"EmptyValueDefaultsToSSH", envWith(""), "ssh"},
{"VSCode", envWith("vscode"), "vscode"},
{"JetBrainsLegacyCasing", envWith("JetBrains"), "jetbrains"},
{"UnknownTypeNormalized", envWith("Cursor-Nightly"), "cursor_nightly"},
{"LastInstanceWins", append(envWith("vscode"), AppNameEnvironmentVariable+"=cursor"), "cursor"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
appName, _, filteredEnv := extractAppName(tc.env)
require.Equal(t, tc.want, appName)
for _, kv := range filteredEnv {
require.NotContains(t, kv, AppNameEnvironmentVariable+"=")
}
})
}
}
func TestAppNameMetricLabel(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
appName string
want string
}{
{"SSH", "ssh", "ssh"},
{"ForkUsesFamily", "cursor", "vscode"},
// No alias for the nightly variant, so it does not get a label.
{"UnlistedVariant", "cursor_nightly", "unknown"},
{"Unknown", "some_new_ide", "unknown"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tc.want, appNameMetricLabel(tc.appName))
})
}
}
func TestSessionCounts(t *testing.T) {
t.Parallel()
s := &Server{sessionCounts: make(map[string]int64)}
// Counters are created on demand and names are normalized.
_ = s.startSession("ssh")
_ = s.startSession("vscode")
endCursor1 := s.startSession("Cursor")
endCursor2 := s.startSession("cursor")
require.Equal(t, map[string]int64{
"ssh": 1,
"vscode": 1,
"cursor": 2,
}, s.SessionCounts())
// Zero-count entries are dropped, not reported as idle apps.
endCursor1()
endCursor2()
require.Equal(t, map[string]int64{
"ssh": 1,
"vscode": 1,
}, s.SessionCounts())
}