Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/pkg/config/checkout_root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package config

import (
"path/filepath"
"testing"
)

// TestCheckoutRootFor pins ProjectConfig.CheckoutRootFor (config.go), which
// maps a monitored repo to its host-local checkout root (kubestellar/hive#5227)
// and was previously untested at 0% coverage. The traversal guard matters:
// the result is a filesystem path built from config strings, and a name of
// ".." or one carrying a separator must never escape CheckoutsDir.
func TestCheckoutRootFor(t *testing.T) {
cases := []struct {
name string
dir string
repo string
want string
}{
{"bare repo name", "/data/checkouts", "hive", filepath.Join("/data/checkouts", "hive")},
{"org-qualified slug uses name only", "/data/checkouts", "kubestellar/hive", filepath.Join("/data/checkouts", "hive")},
{"deep slug uses last segment", "/data/checkouts", "gitlab.com/group/sub/repo", filepath.Join("/data/checkouts", "repo")},
{"empty checkouts dir is a no-op", "", "hive", ""},
{"whitespace-only checkouts dir is a no-op", " ", "hive", ""},
{"empty repo is a no-op", "/data/checkouts", "", ""},
{"slug with trailing slash has no name", "/data/checkouts", "kubestellar/", ""},
{"dot name refused", "/data/checkouts", ".", ""},
{"dotdot name refused", "/data/checkouts", "..", ""},
{"org-qualified dotdot refused", "/data/checkouts", "kubestellar/..", ""},
{"backslash in name refused", "/data/checkouts", `evil\name`, ""},
{"org-qualified backslash refused", "/data/checkouts", `org/..\evil`, ""},
{"surrounding whitespace trimmed", " /data/checkouts ", " hive ", filepath.Join("/data/checkouts", "hive")},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
p := &ProjectConfig{CheckoutsDir: tc.dir}
if got := p.CheckoutRootFor(tc.repo); got != tc.want {
t.Fatalf("CheckoutRootFor(%q) with CheckoutsDir=%q = %q, want %q", tc.repo, tc.dir, got, tc.want)
}
})
}
}

// A refused or unconfigured lookup must return exactly "" — the documented
// no-op sentinel — never a partial path the caller might join or stat.
func TestCheckoutRootFor_NoOpIsEmptyString(t *testing.T) {
p := &ProjectConfig{}
if got := p.CheckoutRootFor("kubestellar/hive"); got != "" {
t.Fatalf("unconfigured CheckoutRootFor = %q, want empty string", got)
}
}
69 changes: 69 additions & 0 deletions src/pkg/dashboard/terminal_urls_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dashboard

import (
"encoding/json"
"net/http"
"testing"
)

// These tests pin the HTTP contract of handleAgentTerminalURLs (#5188),
// previously at 0% coverage: the pipeline behind it (prepareTerminalURLs,
// filterAuthURLs) is tested in terminal_urls_test.go, but the handler's own
// branches — nil deps, the "no pane is not an error" contract, and the
// no-store header — were exercised nowhere.

func decodeTerminalURLs(t *testing.T, body []byte) (urls, authURLs []string) {
t.Helper()
var resp struct {
URLs []string `json:"urls"`
AuthURLs []string `json:"authUrls"`
}
if err := json.Unmarshal(body, &resp); err != nil {
t.Fatalf("response is not the documented JSON shape: %v (body %q)", err, body)
}
if resp.URLs == nil || resp.AuthURLs == nil {
t.Fatalf("urls/authUrls must be empty lists, never null: body %q", body)
}
return resp.URLs, resp.AuthURLs
}

// With no agent manager wired, the endpoint reports 503 rather than panicking
// on a nil dereference — same contract as handleAgentFullLog.
func TestHandleAgentTerminalURLs_NoManager(t *testing.T) {
s, _ := apiServer(t)
s.deps.AgentMgr = nil
rec := doGet(s, "/api/agents/scanner/terminal-urls")
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status = %d, want 503 when AgentMgr is nil", rec.Code)
}
}

// A known-but-not-running agent has no tmux pane to read. Unlike the full-log
// endpoint (which 404s), this endpoint documents "nothing to copy right now"
// as a normal state: 200 with empty lists, so the dashboard hides the control
// instead of surfacing an error.
func TestHandleAgentTerminalURLs_NoActiveSessionIsEmptyNotError(t *testing.T) {
s, _ := apiServer(t)
rec := doGet(s, "/api/agents/scanner/terminal-urls")
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200 for an agent with no active session", rec.Code)
}
urls, authURLs := decodeTerminalURLs(t, rec.Body.Bytes())
if len(urls) != 0 || len(authURLs) != 0 {
t.Fatalf("want empty lists for an agent with no pane, got urls=%v authUrls=%v", urls, authURLs)
}
}

// A nonexistent agent likewise yields empty lists, never an error — the
// handler treats every CaptureFullLog failure as "no pane".
func TestHandleAgentTerminalURLs_UnknownAgentIsEmptyNotError(t *testing.T) {
s, _ := apiServer(t)
rec := doGet(s, "/api/agents/nonexistent/terminal-urls")
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200 for an unknown agent", rec.Code)
}
urls, authURLs := decodeTerminalURLs(t, rec.Body.Bytes())
if len(urls) != 0 || len(authURLs) != 0 {
t.Fatalf("want empty lists for an unknown agent, got urls=%v authUrls=%v", urls, authURLs)
}
}
Loading