- Add Nacos registry for service registration and deregistration. - Implement Redis registry for session management with heartbeat and session claiming. - Improve completion service with session handling and request validation. - Enhance WebSocket handling for completion requests with JSON-RPC support. - Add tests for new registry implementations and completion manager functionalities. - Refactor existing code for better readability and maintainability.
129 lines
3.2 KiB
Go
129 lines
3.2 KiB
Go
package completion
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type fakeRuntimeClient struct {
|
|
closed bool
|
|
}
|
|
|
|
func (f *fakeRuntimeClient) DidOpen(_ context.Context, _ string, _ string, _ int) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeRuntimeClient) DidChange(_ context.Context, _ string, _ string, _ int) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeRuntimeClient) Completion(_ context.Context, _ string, _ int, _ int) (Response, error) {
|
|
return Response{
|
|
Items: []Item{{Label: "ok"}},
|
|
}, nil
|
|
}
|
|
|
|
func (f *fakeRuntimeClient) Close() error {
|
|
f.closed = true
|
|
return nil
|
|
}
|
|
|
|
// 同 language+session 的请求应复用同一个底层 client。
|
|
func TestManagerCompleteSelectsLanguageAndSession(t *testing.T) {
|
|
createCount := 0
|
|
factory := func(_ context.Context, spec LanguageServerSpec, _ string) (RuntimeClient, error) {
|
|
createCount++
|
|
if spec.Language != "go" {
|
|
return nil, fmt.Errorf("unexpected language: %s", spec.Language)
|
|
}
|
|
return &fakeRuntimeClient{}, nil
|
|
}
|
|
|
|
m := NewManager(ManagerConfig{WorkspaceDir: "."}, []LanguageServerSpec{
|
|
{Language: "go", LanguageID: "go", Command: "gopls"},
|
|
}, factory)
|
|
defer m.Close()
|
|
|
|
for i := 0; i < 2; i++ {
|
|
resp, err := m.Complete(context.Background(), Request{
|
|
Language: "go",
|
|
SessionID: "s1",
|
|
URI: "file:///main.go",
|
|
Text: "package main",
|
|
Line: 0,
|
|
Character: 0,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Complete() error = %v", err)
|
|
}
|
|
if len(resp.Items) != 1 || resp.Items[0].Label != "ok" {
|
|
t.Fatalf("unexpected response: %+v", resp)
|
|
}
|
|
}
|
|
|
|
if createCount != 1 {
|
|
t.Fatalf("expected one client creation, got %d", createCount)
|
|
}
|
|
}
|
|
|
|
// 未配置的语言应返回 ErrUnsupportedLanguage。
|
|
func TestManagerCompleteUnsupportedLanguage(t *testing.T) {
|
|
m := NewManager(ManagerConfig{}, []LanguageServerSpec{
|
|
{Language: "go", LanguageID: "go", Command: "gopls"},
|
|
}, func(_ context.Context, _ LanguageServerSpec, _ string) (RuntimeClient, error) {
|
|
return &fakeRuntimeClient{}, nil
|
|
})
|
|
defer m.Close()
|
|
|
|
_, err := m.Complete(context.Background(), Request{
|
|
Language: "python",
|
|
URI: "file:///main.py",
|
|
Text: "print('hi')",
|
|
Line: 0,
|
|
Character: 0,
|
|
})
|
|
if !errors.Is(err, ErrUnsupportedLanguage) {
|
|
t.Fatalf("expected ErrUnsupportedLanguage, got %v", err)
|
|
}
|
|
}
|
|
|
|
// 超过空闲 TTL 的会话应被后台清理并关闭 client。
|
|
func TestManagerCleanupIdleSession(t *testing.T) {
|
|
client := &fakeRuntimeClient{}
|
|
m := NewManager(ManagerConfig{
|
|
WorkspaceDir: ".",
|
|
SessionTTL: 30 * time.Millisecond,
|
|
CleanupInterval: 10 * time.Millisecond,
|
|
}, []LanguageServerSpec{
|
|
{Language: "go", LanguageID: "go", Command: "gopls"},
|
|
}, func(_ context.Context, _ LanguageServerSpec, _ string) (RuntimeClient, error) {
|
|
return client, nil
|
|
})
|
|
defer m.Close()
|
|
|
|
_, err := m.Complete(context.Background(), Request{
|
|
Language: "go",
|
|
SessionID: "s2",
|
|
URI: "file:///main.go",
|
|
Text: "package main",
|
|
Line: 0,
|
|
Character: 0,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Complete() error = %v", err)
|
|
}
|
|
|
|
time.Sleep(90 * time.Millisecond)
|
|
|
|
sessions := m.ActiveSessions()
|
|
if sessions["go"] != 0 {
|
|
t.Fatalf("expected session cleanup, got %+v", sessions)
|
|
}
|
|
if !client.closed {
|
|
t.Fatal("expected client to be closed by cleanup")
|
|
}
|
|
}
|