[agent/claude:opus] DX audit and fix. 1) Review CLAUDE.md — update any outdate... #1
5 changed files with 259 additions and 6 deletions
5
go.mod
5
go.mod
|
|
@ -11,12 +11,15 @@ require (
|
|||
forge.lthn.ai/core/go-ws v0.2.3
|
||||
forge.lthn.ai/core/gui v0.1.3
|
||||
forge.lthn.ai/core/mcp v0.3.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.74
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
|
|
@ -28,7 +31,7 @@ require (
|
|||
dario.cat/mergo v1.0.2 // indirect
|
||||
forge.lthn.ai/core/go-ai v0.1.11 // indirect
|
||||
forge.lthn.ai/core/go-io v0.1.5 // indirect
|
||||
forge.lthn.ai/core/go-log v0.0.4 // indirect
|
||||
forge.lthn.ai/core/go-log v0.0.4
|
||||
forge.lthn.ai/core/go-rag v0.1.9 // indirect
|
||||
forge.lthn.ai/core/go-webview v0.1.5 // indirect
|
||||
github.com/99designs/gqlgen v0.17.88 // indirect
|
||||
|
|
|
|||
33
main_test.go
Normal file
33
main_test.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forge.lthn.ai/core/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGuiEnabled_Good_NilConfig(t *testing.T) {
|
||||
// nil config should fall through to display detection.
|
||||
result := guiEnabled(nil)
|
||||
// On macOS/Windows this returns true; on Linux it depends on DISPLAY.
|
||||
// Just verify it doesn't panic.
|
||||
_ = result
|
||||
}
|
||||
|
||||
func TestGuiEnabled_Good_WithConfig(t *testing.T) {
|
||||
cfg, _ := config.New()
|
||||
// Fresh config has no gui.enabled key — should fall through to OS detection.
|
||||
result := guiEnabled(cfg)
|
||||
_ = result
|
||||
}
|
||||
|
||||
func TestStaticAssetGroup_Good(t *testing.T) {
|
||||
s := &staticAssetGroup{
|
||||
name: "test-assets",
|
||||
basePath: "/assets/test",
|
||||
dir: "/tmp",
|
||||
}
|
||||
assert.Equal(t, "test-assets", s.Name())
|
||||
assert.Equal(t, "/assets/test", s.BasePath())
|
||||
}
|
||||
86
providers_test.go
Normal file
86
providers_test.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"forge.lthn.ai/core/api/pkg/provider"
|
||||
"forge.lthn.ai/core/go-scm/manifest"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProvidersAPI_Name(t *testing.T) {
|
||||
api := NewProvidersAPI(nil, nil)
|
||||
assert.Equal(t, "providers-api", api.Name())
|
||||
}
|
||||
|
||||
func TestProvidersAPI_BasePath(t *testing.T) {
|
||||
api := NewProvidersAPI(nil, nil)
|
||||
assert.Equal(t, "/api/v1/providers", api.BasePath())
|
||||
}
|
||||
|
||||
func TestProvidersAPI_List_Good_Empty(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
reg := provider.NewRegistry()
|
||||
rm := NewRuntimeManager(nil)
|
||||
api := NewProvidersAPI(reg, rm)
|
||||
|
||||
router := gin.New()
|
||||
rg := router.Group(api.BasePath())
|
||||
api.RegisterRoutes(rg)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/api/v1/providers", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp providersResponse
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, resp.Providers)
|
||||
}
|
||||
|
||||
func TestProvidersAPI_List_Good_WithRuntimeProviders(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
reg := provider.NewRegistry()
|
||||
rm := NewRuntimeManager(nil)
|
||||
|
||||
// Simulate a runtime provider.
|
||||
rm.providers = append(rm.providers, &RuntimeProvider{
|
||||
Dir: "/tmp/test",
|
||||
Port: 9999,
|
||||
Manifest: &manifest.Manifest{
|
||||
Code: "test-provider",
|
||||
Name: "Test Provider",
|
||||
Version: "0.1.0",
|
||||
Namespace: "test",
|
||||
},
|
||||
})
|
||||
|
||||
api := NewProvidersAPI(reg, rm)
|
||||
|
||||
router := gin.New()
|
||||
rg := router.Group(api.BasePath())
|
||||
api.RegisterRoutes(rg)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/api/v1/providers", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp providersResponse
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, resp.Providers, 1)
|
||||
assert.Equal(t, "test-provider", resp.Providers[0].Name)
|
||||
assert.Equal(t, "test", resp.Providers[0].BasePath)
|
||||
assert.Equal(t, "active", resp.Providers[0].Status)
|
||||
}
|
||||
11
runtime.go
11
runtime.go
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
"forge.lthn.ai/core/api"
|
||||
"forge.lthn.ai/core/api/pkg/provider"
|
||||
coreerr "forge.lthn.ai/core/go-log"
|
||||
"forge.lthn.ai/core/go-scm/manifest"
|
||||
"forge.lthn.ai/core/go-scm/marketplace"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -63,7 +64,7 @@ func (rm *RuntimeManager) StartAll(ctx context.Context) error {
|
|||
dir := defaultProvidersDir()
|
||||
discovered, err := marketplace.DiscoverProviders(dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runtime: discover providers: %w", err)
|
||||
return coreerr.E("runtime.StartAll", "discover providers", err)
|
||||
}
|
||||
|
||||
if len(discovered) == 0 {
|
||||
|
|
@ -93,7 +94,7 @@ func (rm *RuntimeManager) startProvider(ctx context.Context, dp marketplace.Disc
|
|||
// Assign a free port.
|
||||
port, err := findFreePort()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("find free port: %w", err)
|
||||
return nil, coreerr.E("runtime.startProvider", "find free port", err)
|
||||
}
|
||||
|
||||
// Resolve binary path.
|
||||
|
|
@ -114,7 +115,7 @@ func (rm *RuntimeManager) startProvider(ctx context.Context, dp marketplace.Disc
|
|||
cmd.Stderr = os.Stderr
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("start binary %s: %w", binaryPath, err)
|
||||
return nil, coreerr.E("runtime.startProvider", fmt.Sprintf("start binary %s", binaryPath), err)
|
||||
}
|
||||
|
||||
// Wait for health check.
|
||||
|
|
@ -122,7 +123,7 @@ func (rm *RuntimeManager) startProvider(ctx context.Context, dp marketplace.Disc
|
|||
if err := waitForHealth(healthURL, 10*time.Second); err != nil {
|
||||
// Kill the process if health check fails.
|
||||
_ = cmd.Process.Kill()
|
||||
return nil, fmt.Errorf("health check failed for %s: %w", m.Code, err)
|
||||
return nil, coreerr.E("runtime.startProvider", fmt.Sprintf("health check failed for %s", m.Code), err)
|
||||
}
|
||||
|
||||
// Register proxy provider.
|
||||
|
|
@ -248,7 +249,7 @@ func waitForHealth(url string, timeout time.Duration) error {
|
|||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
return fmt.Errorf("health check timed out after %s: %s", timeout, url)
|
||||
return coreerr.E("runtime.waitForHealth", fmt.Sprintf("timed out after %s: %s", timeout, url), nil)
|
||||
}
|
||||
|
||||
// staticAssetGroup is a simple RouteGroup that serves static files.
|
||||
|
|
|
|||
130
runtime_test.go
Normal file
130
runtime_test.go
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"forge.lthn.ai/core/go-scm/manifest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFindFreePort_Good(t *testing.T) {
|
||||
port, err := findFreePort()
|
||||
require.NoError(t, err)
|
||||
assert.Greater(t, port, 0)
|
||||
assert.Less(t, port, 65536)
|
||||
}
|
||||
|
||||
func TestFindFreePort_UniquePerCall(t *testing.T) {
|
||||
port1, err := findFreePort()
|
||||
require.NoError(t, err)
|
||||
port2, err := findFreePort()
|
||||
require.NoError(t, err)
|
||||
// Two consecutive calls should very likely return different ports.
|
||||
// (Not guaranteed, but effectively always true.)
|
||||
assert.NotEqual(t, port1, port2)
|
||||
}
|
||||
|
||||
func TestWaitForHealth_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
err := waitForHealth(srv.URL, 5*time.Second)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestWaitForHealth_Bad_Timeout(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
err := waitForHealth(srv.URL, 500*time.Millisecond)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "timed out")
|
||||
}
|
||||
|
||||
func TestWaitForHealth_Bad_NoServer(t *testing.T) {
|
||||
err := waitForHealth("http://127.0.0.1:1", 500*time.Millisecond)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "timed out")
|
||||
}
|
||||
|
||||
func TestDefaultProvidersDir_Good(t *testing.T) {
|
||||
dir := defaultProvidersDir()
|
||||
assert.Contains(t, dir, ".core")
|
||||
assert.Contains(t, dir, "providers")
|
||||
}
|
||||
|
||||
func TestRuntimeManager_List_Good_Empty(t *testing.T) {
|
||||
rm := NewRuntimeManager(nil)
|
||||
infos := rm.List()
|
||||
assert.Empty(t, infos)
|
||||
}
|
||||
|
||||
func TestRuntimeManager_List_Good_WithProviders(t *testing.T) {
|
||||
rm := NewRuntimeManager(nil)
|
||||
rm.providers = []*RuntimeProvider{
|
||||
{
|
||||
Dir: "/tmp/test-provider",
|
||||
Port: 12345,
|
||||
Manifest: &manifest.Manifest{
|
||||
Code: "test-svc",
|
||||
Name: "Test Service",
|
||||
Version: "1.0.0",
|
||||
Namespace: "test",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
infos := rm.List()
|
||||
require.Len(t, infos, 1)
|
||||
assert.Equal(t, "test-svc", infos[0].Code)
|
||||
assert.Equal(t, "Test Service", infos[0].Name)
|
||||
assert.Equal(t, "1.0.0", infos[0].Version)
|
||||
assert.Equal(t, "test", infos[0].Namespace)
|
||||
assert.Equal(t, 12345, infos[0].Port)
|
||||
assert.Equal(t, "/tmp/test-provider", infos[0].Dir)
|
||||
}
|
||||
|
||||
func TestRuntimeManager_StopAll_Good_Empty(t *testing.T) {
|
||||
rm := NewRuntimeManager(nil)
|
||||
// Should not panic with no providers.
|
||||
rm.StopAll()
|
||||
assert.Empty(t, rm.providers)
|
||||
}
|
||||
|
||||
func TestRuntimeManager_StopAll_Good_WithProcess(t *testing.T) {
|
||||
// Start a real process so we can test graceful stop.
|
||||
cmd := exec.CommandContext(context.Background(), "sleep", "60")
|
||||
require.NoError(t, cmd.Start())
|
||||
|
||||
rm := NewRuntimeManager(nil)
|
||||
rm.providers = []*RuntimeProvider{
|
||||
{
|
||||
Manifest: &manifest.Manifest{Code: "sleeper"},
|
||||
Cmd: cmd,
|
||||
},
|
||||
}
|
||||
|
||||
rm.StopAll()
|
||||
assert.Nil(t, rm.providers)
|
||||
}
|
||||
|
||||
func TestRuntimeManager_StartAll_Good_EmptyDir(t *testing.T) {
|
||||
rm := NewRuntimeManager(nil)
|
||||
// StartAll with a non-existent providers dir should return an error
|
||||
// because the default dir won't have providers (at most it logs and returns nil).
|
||||
err := rm.StartAll(context.Background())
|
||||
// Depending on whether ~/.core/providers/ exists, this either returns
|
||||
// nil (no providers found) or an error (dir doesn't exist).
|
||||
// Either outcome is acceptable — no panic.
|
||||
_ = err
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue