Files
Pulse/internal/api/config_profiles_test.go
rcourtman f2541b0d6c Refactor: Multi-tenancy support for API and License handlers
- Updated LicenseHandlers and LicenseService to be context/tenant aware
- Refactored API router and middleware to support tenant-scoped license checks
- Updated associated tests for context-aware handlers
2026-01-22 16:42:39 +00:00

322 lines
8.8 KiB
Go

package api
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
)
func TestConfigProfileHandlers(t *testing.T) {
tempDir := t.TempDir()
mtp := config.NewMultiTenantPersistence(tempDir)
// Ensure default persistence exists
_, err := mtp.GetPersistence("default")
if err != nil {
t.Fatalf("Failed to initialize default persistence: %v", err)
}
handler := NewConfigProfileHandler(mtp)
// 1. List Profiles (Empty)
t.Run("ListProfilesEmpty", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", rec.Code)
}
var profiles []models.AgentProfile
if err := json.NewDecoder(rec.Body).Decode(&profiles); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if len(profiles) != 0 {
t.Errorf("expected 0 profiles, got %d", len(profiles))
}
})
var profileID string
// 2. Create Profile
t.Run("CreateProfile", func(t *testing.T) {
profile := models.AgentProfile{
Name: "Test Profile",
Config: map[string]interface{}{
"log_level": "debug",
"interval": "30s",
},
}
body, _ := json.Marshal(profile)
req := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(body))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String())
}
var created models.AgentProfile
if err := json.NewDecoder(rec.Body).Decode(&created); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if created.Name != "Test Profile" {
t.Errorf("expected name 'Test Profile', got %q", created.Name)
}
if created.ID == "" {
t.Error("expected non-empty ID")
}
profileID = created.ID
})
// 3. List Profiles (1 Profile)
t.Run("ListProfilesOne", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
var profiles []models.AgentProfile
json.NewDecoder(rec.Body).Decode(&profiles)
if len(profiles) != 1 {
t.Errorf("expected 1 profile, got %d", len(profiles))
}
if profiles[0].ID != profileID {
t.Errorf("expected ID %s, got %s", profileID, profiles[0].ID)
}
})
// 4. Update Profile
t.Run("UpdateProfile", func(t *testing.T) {
update := models.AgentProfile{
Name: "Updated Profile",
Config: map[string]interface{}{
"log_level": "info",
},
}
body, _ := json.Marshal(update)
req := httptest.NewRequest(http.MethodPut, "/"+profileID, bytes.NewBuffer(body))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String())
}
var updated models.AgentProfile
json.NewDecoder(rec.Body).Decode(&updated)
if updated.Name != "Updated Profile" {
t.Errorf("expected updated name, got %q", updated.Name)
}
})
// 5. List Assignments (Empty)
t.Run("ListAssignmentsEmpty", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/assignments", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
var assignments []models.AgentProfileAssignment
json.NewDecoder(rec.Body).Decode(&assignments)
if len(assignments) != 0 {
t.Errorf("expected 0 assignments, got %d", len(assignments))
}
})
// 6. Assign Profile
t.Run("AssignProfile", func(t *testing.T) {
assignment := models.AgentProfileAssignment{
AgentID: "test-agent",
ProfileID: profileID,
}
body, _ := json.Marshal(assignment)
req := httptest.NewRequest(http.MethodPost, "/assignments", bytes.NewBuffer(body))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
var created models.AgentProfileAssignment
json.NewDecoder(rec.Body).Decode(&created)
if created.AgentID != "test-agent" || created.ProfileID != profileID {
t.Errorf("assignment mismatch: %+v", created)
}
})
// 7. List Assignments (1 Assignment)
t.Run("ListAssignmentsOne", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/assignments", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
var assignments []models.AgentProfileAssignment
if err := json.NewDecoder(rec.Body).Decode(&assignments); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if len(assignments) != 1 {
t.Errorf("expected 1 assignment, got %d", len(assignments))
}
})
// 8. Unassign Profile
t.Run("UnassignProfile", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/assignments/test-agent", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusNoContent {
t.Fatalf("expected status 204, got %d: %s", rec.Code, rec.Body.String())
}
req = httptest.NewRequest(http.MethodGet, "/assignments", nil)
rec = httptest.NewRecorder()
handler.ServeHTTP(rec, req)
var assignments []models.AgentProfileAssignment
if err := json.NewDecoder(rec.Body).Decode(&assignments); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if len(assignments) != 0 {
t.Errorf("expected 0 assignments after unassign, got %d", len(assignments))
}
})
// 9. Assign Profile (Cleanup on Delete)
t.Run("AssignProfileForDeleteCleanup", func(t *testing.T) {
assignment := models.AgentProfileAssignment{
AgentID: "cleanup-agent",
ProfileID: profileID,
}
body, _ := json.Marshal(assignment)
req := httptest.NewRequest(http.MethodPost, "/assignments", bytes.NewBuffer(body))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String())
}
})
// 10. Delete Profile
t.Run("DeleteProfile", func(t *testing.T) {
req := httptest.NewRequest(http.MethodDelete, "/"+profileID, nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", rec.Code)
}
// Verify deleted
req = httptest.NewRequest(http.MethodGet, "/", nil)
rec = httptest.NewRecorder()
handler.ServeHTTP(rec, req)
var profiles []models.AgentProfile
json.NewDecoder(rec.Body).Decode(&profiles)
if len(profiles) != 0 {
t.Errorf("expected 0 profiles after delete, got %d", len(profiles))
}
req = httptest.NewRequest(http.MethodGet, "/assignments", nil)
rec = httptest.NewRecorder()
handler.ServeHTTP(rec, req)
var assignments []models.AgentProfileAssignment
if err := json.NewDecoder(rec.Body).Decode(&assignments); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if len(assignments) != 0 {
t.Errorf("expected 0 assignments after profile delete, got %d", len(assignments))
}
})
// 11. Get Schema
t.Run("GetSchema", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/schema", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
var defs []models.ConfigKeyDefinition
if err := json.NewDecoder(rec.Body).Decode(&defs); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if len(defs) == 0 {
t.Fatalf("expected schema definitions, got 0")
}
found := false
for _, def := range defs {
if def.Key == "interval" {
found = true
break
}
}
if !found {
t.Errorf("expected schema to include interval")
}
})
// 12. Validate Config
t.Run("ValidateConfig", func(t *testing.T) {
payload := map[string]interface{}{
"interval": 5,
"unknown_key": true,
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest(http.MethodPost, "/validate", bytes.NewBuffer(body))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d: %s", rec.Code, rec.Body.String())
}
var result models.ValidationResult
if err := json.NewDecoder(rec.Body).Decode(&result); err != nil {
t.Fatalf("failed to decode: %v", err)
}
if result.Valid {
t.Fatalf("expected invalid result, got valid")
}
if len(result.Errors) == 0 {
t.Fatalf("expected validation errors, got none")
}
if len(result.Warnings) == 0 {
t.Fatalf("expected validation warnings, got none")
}
hasIntervalError := false
for _, err := range result.Errors {
if err.Key == "interval" {
hasIntervalError = true
break
}
}
if !hasIntervalError {
t.Errorf("expected interval error, got %+v", result.Errors)
}
hasUnknownWarning := false
for _, warn := range result.Warnings {
if warn.Key == "unknown_key" {
hasUnknownWarning = true
break
}
}
if !hasUnknownWarning {
t.Errorf("expected unknown_key warning, got %+v", result.Warnings)
}
})
}