feat/updates #1

Merged
Snider merged 51 commits from feat/updates into dev 2026-02-16 05:54:07 +00:00
2 changed files with 146 additions and 0 deletions
Showing only changes of commit 4134c58488 - Show all commits

View file

@ -8,9 +8,12 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"sync"
"time"
"github.com/host-uk/core/pkg/forge"
)
// HubService coordinates with the agentic portal for issue assignment and leaderboard.
@ -207,3 +210,80 @@ func (h *HubService) loadPendingOps() {}
// savePendingOps is a no-op placeholder (disk persistence comes in Task 7).
func (h *HubService) savePendingOps() {}
// drainPendingOps replays queued operations (no-op until Task 7).
func (h *HubService) drainPendingOps() {}
// AutoRegister exchanges a Forge API token for a hub API key.
// If a hub token is already configured, this is a no-op.
func (h *HubService) AutoRegister() error {
// Skip if already registered.
if h.config.GetHubToken() != "" {
return nil
}
hubURL := h.config.GetHubURL()
if hubURL == "" {
return fmt.Errorf("hub URL not configured")
}
// Resolve forge credentials from config/env.
forgeURL := h.config.GetForgeURL()
forgeToken := h.config.GetForgeToken()
if forgeToken == "" {
resolvedURL, resolvedToken, err := forge.ResolveConfig(forgeURL, "")
if err != nil {
return fmt.Errorf("resolve forge config: %w", err)
}
forgeURL = resolvedURL
forgeToken = resolvedToken
}
if forgeToken == "" {
return fmt.Errorf("no forge token available (set FORGE_TOKEN or run: core forge config --token TOKEN)")
}
// Build request body.
payload := map[string]string{
"forge_url": forgeURL,
"forge_token": forgeToken,
"client_id": h.config.GetClientID(),
}
data, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("marshal auto-register body: %w", err)
}
// POST directly (no bearer token yet).
resp, err := h.client.Post(hubURL+"/api/bugseti/auth/forge", "application/json", bytes.NewReader(data))
if err != nil {
h.mu.Lock()
h.connected = false
h.mu.Unlock()
return fmt.Errorf("auto-register request: %w", err)
}
defer resp.Body.Close()
h.mu.Lock()
h.connected = true
h.mu.Unlock()
if resp.StatusCode >= 400 {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("auto-register failed %d: %s", resp.StatusCode, string(respBody))
}
var result struct {
APIKey string `json:"api_key"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("decode auto-register response: %w", err)
}
if err := h.config.SetHubToken(result.APIKey); err != nil {
return fmt.Errorf("cache hub token: %w", err)
}
log.Printf("BugSETI: auto-registered with hub, token cached")
return nil
}

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/stretchr/testify/assert"
@ -108,3 +109,68 @@ func TestDoRequest_Bad_NetworkError(t *testing.T) {
assert.Error(t, err)
assert.False(t, h.IsConnected())
}
// ---- AutoRegister ----
func TestAutoRegister_Good(t *testing.T) {
var gotBody map[string]string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "/api/bugseti/auth/forge", r.URL.Path)
assert.Equal(t, "POST", r.Method)
_ = json.NewDecoder(r.Body).Decode(&gotBody)
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`{"api_key":"ak_test_12345"}`))
}))
defer srv.Close()
cfg := testConfigService(t, nil, nil)
cfg.config.HubURL = srv.URL
cfg.config.ForgeURL = "https://forge.example.com"
cfg.config.ForgeToken = "forge-tok-abc"
h := NewHubService(cfg)
err := h.AutoRegister()
require.NoError(t, err)
// Verify token was cached.
assert.Equal(t, "ak_test_12345", h.config.GetHubToken())
// Verify request body.
assert.Equal(t, "https://forge.example.com", gotBody["forge_url"])
assert.Equal(t, "forge-tok-abc", gotBody["forge_token"])
assert.NotEmpty(t, gotBody["client_id"])
}
func TestAutoRegister_Bad_NoForgeToken(t *testing.T) {
// Isolate from user's real ~/.core/config.yaml and env vars.
origHome := os.Getenv("HOME")
t.Setenv("HOME", t.TempDir())
t.Setenv("FORGE_TOKEN", "")
t.Setenv("FORGE_URL", "")
defer os.Setenv("HOME", origHome)
cfg := testConfigService(t, nil, nil)
cfg.config.HubURL = "https://hub.example.com"
// No forge token set, and env/config are empty in test.
h := NewHubService(cfg)
err := h.AutoRegister()
require.Error(t, err)
assert.Contains(t, err.Error(), "no forge token available")
}
func TestAutoRegister_Good_SkipsIfAlreadyRegistered(t *testing.T) {
cfg := testConfigService(t, nil, nil)
cfg.config.HubURL = "https://hub.example.com"
cfg.config.HubToken = "existing-token"
h := NewHubService(cfg)
err := h.AutoRegister()
require.NoError(t, err)
// Token should remain unchanged.
assert.Equal(t, "existing-token", h.config.GetHubToken())
}