Files
dingtalk-workspace-cli/internal/app/root_cache_refresh_test.go
T
2026-03-27 17:55:57 +08:00

162 lines
4.8 KiB
Go

package app
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cache"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/market"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/transport"
)
func TestCacheRefreshClearsExistingCachesAndSkipsCLISkippedServers(t *testing.T) {
cacheDir := t.TempDir()
t.Setenv(cli.CacheDirEnv, cacheDir)
var skippedRuntimeCalls atomic.Int32
var srv *httptest.Server
srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/cli/discovery/apis":
_ = json.NewEncoder(w).Encode(market.ListResponse{
Metadata: market.ListMetadata{Count: 2},
Servers: []market.ServerEnvelope{
{
Server: market.RegistryServer{
Name: "Active Service",
Remotes: []market.RegistryRemote{
{Type: "streamable-http", URL: srv.URL + "/mcp/active"},
},
},
Meta: market.EnvelopeMeta{
Registry: market.RegistryMetadata{Status: "active"},
CLI: market.CLIOverlay{ID: "active", Command: "active"},
},
},
{
Server: market.RegistryServer{
Name: "Skipped Service",
Remotes: []market.RegistryRemote{
{Type: "streamable-http", URL: srv.URL + "/mcp/skipped"},
},
},
Meta: market.EnvelopeMeta{
Registry: market.RegistryMetadata{Status: "active"},
CLI: market.CLIOverlay{ID: "legacy", Command: "legacy", Skip: true},
},
},
},
})
case "/mcp/active":
http.Error(w, "active runtime unavailable", http.StatusInternalServerError)
case "/mcp/skipped":
skippedRuntimeCalls.Add(1)
http.Error(w, "skipped runtime should not be called", http.StatusInternalServerError)
default:
http.NotFound(w, r)
}
}))
defer srv.Close()
store := cache.NewStore(cacheDir)
const partition = "default/default"
activeKey := market.ServerKey(srv.URL + "/mcp/active")
skippedKey := market.ServerKey(srv.URL + "/mcp/skipped")
saveCachedRuntimeAndDetail(t, store, partition, activeKey)
saveCachedRuntimeAndDetail(t, store, partition, skippedKey)
saveCLIIDDetail(t, store, partition, "active")
SetDiscoveryBaseURL(srv.URL)
t.Cleanup(func() { SetDiscoveryBaseURL("") })
cmd := newCacheCommand()
var out bytes.Buffer
cmd.SetOut(&out)
cmd.SetErr(&out)
cmd.SetArgs([]string{"refresh"})
if err := cmd.Execute(); err != nil {
t.Fatalf("Execute() error = %v", err)
}
if _, _, err := store.LoadTools(partition, activeKey); err == nil {
t.Fatal("LoadTools(active) error = nil, want cache cleared before failed refresh")
}
if _, _, err := store.LoadDetail(partition, activeKey); err == nil {
t.Fatal("LoadDetail(active) error = nil, want detail cache cleared before failed refresh")
}
if _, _, err := store.LoadDetail(partition, "active"); err != nil {
t.Fatalf("LoadDetail(active CLI.ID) error = %v, want CLI metadata preserved on failed refresh", err)
}
if _, _, err := store.LoadTools(partition, skippedKey); err == nil {
t.Fatal("LoadTools(skipped) error = nil, want skipped service cache removed")
}
if _, _, err := store.LoadDetail(partition, skippedKey); err == nil {
t.Fatal("LoadDetail(skipped) error = nil, want skipped service detail cache removed")
}
if got := skippedRuntimeCalls.Load(); got != 0 {
t.Fatalf("skipped runtime calls = %d, want 0", got)
}
}
func saveCLIIDDetail(t *testing.T, store *cache.Store, partition, cliID string) {
t.Helper()
payload, err := json.Marshal(market.DetailResponse{
Success: true,
Result: market.DetailResult{
Tools: []market.DetailTool{
{ToolName: "stale_tool", ToolTitle: "Stale Tool"},
},
},
})
if err != nil {
t.Fatalf("json.Marshal(cli detail payload) error = %v", err)
}
if err := store.SaveDetail(partition, cliID, cache.DetailSnapshot{
MCPID: 0,
Payload: payload,
}); err != nil {
t.Fatalf("SaveDetail(%s) error = %v", cliID, err)
}
}
func saveCachedRuntimeAndDetail(t *testing.T, store *cache.Store, partition, serverKey string) {
t.Helper()
if err := store.SaveTools(partition, serverKey, cache.ToolsSnapshot{
ServerKey: serverKey,
ProtocolVersion: "2025-03-26",
Tools: []transport.ToolDescriptor{
{Name: "stale_tool", Title: "Stale Tool"},
},
}); err != nil {
t.Fatalf("SaveTools(%s) error = %v", serverKey, err)
}
payload, err := json.Marshal(market.DetailResponse{
Success: true,
Result: market.DetailResult{
Tools: []market.DetailTool{
{ToolName: "stale_tool", ToolTitle: "Stale Tool"},
},
},
})
if err != nil {
t.Fatalf("json.Marshal(detail payload) error = %v", err)
}
if err := store.SaveDetail(partition, serverKey, cache.DetailSnapshot{
MCPID: 0,
Payload: payload,
}); err != nil {
t.Fatalf("SaveDetail(%s) error = %v", serverKey, err)
}
}