162 lines
4.8 KiB
Go
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)
|
|
}
|
|
}
|