Pulse/internal/api/config_handlers_setup_url_test.go

184 lines
5 KiB
Go

package api
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
internalauth "github.com/rcourtman/pulse-go-rewrite/pkg/auth"
)
func TestHandleSetupScriptURL(t *testing.T) {
tempDir, err := os.MkdirTemp("", "pulse-setup-url-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
dummyCfg := &config.Config{
FrontendPort: 8080,
PublicURL: "https://pulse.example.com",
}
dummyCfg.DataPath = tempDir
handler := newTestConfigHandlers(t, dummyCfg)
tests := []struct {
name string
requestBody interface{}
expectedStatus int
verifyResponse func(*testing.T, map[string]interface{}, *ConfigHandlers)
}{
{
name: "success_valid_request",
requestBody: map[string]interface{}{
"type": "pve",
"host": "pve1.local",
},
expectedStatus: http.StatusOK,
verifyResponse: func(t *testing.T, resp map[string]interface{}, h *ConfigHandlers) {
url, ok := resp["url"].(string)
if !ok || url == "" {
t.Errorf("expected valid url, got %v", resp["url"])
}
token, ok := resp["setupToken"].(string)
if !ok || token == "" {
t.Errorf("expected valid setupToken, got %v", resp["setupToken"])
}
// Verify URL construction uses public URL host
if !strings.Contains(url, "pulse.example.com") {
t.Errorf("expected URL to contain public host, got %s", url)
}
},
},
{
name: "fail_invalid_json",
requestBody: "invalid-json", // Will fail JSON decoding
expectedStatus: http.StatusBadRequest,
verifyResponse: nil,
},
{
name: "success_missing_fields_defaults",
requestBody: map[string]interface{}{}, // Empty body might be valid if fields are optional?
// Looking at code: it just decodes. If type/host are empty, it proceeds to generate token.
// But type is used in setupCode struct.
expectedStatus: http.StatusOK,
verifyResponse: func(t *testing.T, resp map[string]interface{}, h *ConfigHandlers) {
// Should still return a valid response even with empty fields
if _, ok := resp["url"]; !ok {
t.Errorf("expected url field in response")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var body []byte
var err error
if s, ok := tt.requestBody.(string); ok && s == "invalid-json" {
body = []byte(s)
} else {
body, err = json.Marshal(tt.requestBody)
if err != nil {
t.Fatal(err)
}
}
req := httptest.NewRequest("POST", "/api/setup/url", bytes.NewBuffer(body))
req.Host = "127.0.0.1:8080" // Simulate loopback request to trigger PublicURL usage logic
w := httptest.NewRecorder()
handler.HandleSetupScriptURL(w, req)
if w.Code != tt.expectedStatus {
t.Errorf("handler returned wrong status code: got %v want %v", w.Code, tt.expectedStatus)
}
if tt.verifyResponse != nil && w.Code == http.StatusOK {
var response map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
t.Errorf("failed to decode response: %v", err)
}
tt.verifyResponse(t, response, handler)
}
})
}
}
func TestHandleSetupScriptURL_MethodNotAllowed(t *testing.T) {
tempDir, err := os.MkdirTemp("", "pulse-setup-url-test-method")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
cfg := &config.Config{DataPath: tempDir}
handler := newTestConfigHandlers(t, cfg)
req := httptest.NewRequest("GET", "/api/setup/url", nil)
w := httptest.NewRecorder()
handler.HandleSetupScriptURL(w, req)
if w.Code != http.StatusMethodNotAllowed {
t.Errorf("expected method not allowed, got %v", w.Code)
}
}
func TestHandleSetupScriptURLIgnoresOrgContext(t *testing.T) {
tempDir, err := os.MkdirTemp("", "pulse-setup-url-test-org")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tempDir)
cfg := &config.Config{
DataPath: tempDir,
FrontendPort: 8080,
PublicURL: "https://pulse.example.com",
}
handler := newTestConfigHandlers(t, cfg)
body := bytes.NewBufferString(`{"type":"pve","host":"delly"}`)
req := httptest.NewRequest(http.MethodPost, "/api/setup-script-url", body)
req = req.WithContext(context.WithValue(req.Context(), OrgIDContextKey, "acme"))
req.Host = "127.0.0.1:8080"
w := httptest.NewRecorder()
handler.HandleSetupScriptURL(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var resp map[string]interface{}
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
token, ok := resp["setupToken"].(string)
if !ok || token == "" {
t.Fatalf("expected setup token in response, got %#v", resp["setupToken"])
}
tokenHash := internalauth.HashAPIToken(token)
handler.codeMutex.RLock()
setupCode := handler.setupCodes[tokenHash]
handler.codeMutex.RUnlock()
if setupCode == nil {
t.Fatalf("expected setup code to be stored for token hash %q", tokenHash)
}
if setupCode.OrgID != "default" {
t.Fatalf("expected setup code org to be forced to default, got %q", setupCode.OrgID)
}
}