Changeset 0.22.7 (#149)

This commit is contained in:
2026-03-04 10:44:42 +00:00
parent d8e0664fa3
commit 389e47b0f9
62 changed files with 6820 additions and 1476 deletions

View File

@@ -200,12 +200,12 @@ func setupHarness(t *testing.T) *testHarness {
settings := NewSettingsHandler(nil)
protected.GET("/profile", settings.GetProfile)
// Presets
presets := NewPersonaHandler(stores)
protected.GET("/presets", presets.ListUserPersonas)
protected.POST("/presets", presets.CreateUserPersona)
protected.GET("/presets/:id/knowledge-bases", presets.GetPersonaKBs) // v0.17.0
protected.PUT("/presets/:id/knowledge-bases", presets.SetPersonaKBs) // v0.17.0
// Personas
personas := NewPersonaHandler(stores)
protected.GET("/personas", personas.ListUserPersonas)
protected.POST("/personas", personas.CreateUserPersona)
protected.GET("/personas/:id/knowledge-bases", personas.GetPersonaKBs) // v0.17.0
protected.PUT("/personas/:id/knowledge-bases", personas.SetPersonaKBs) // v0.17.0
// Notes
notes := NewNoteHandler(stores)
@@ -283,10 +283,10 @@ func setupHarness(t *testing.T) *testHarness {
admin.GET("/teams/:id", teams.GetTeam)
admin.GET("/teams/:id/members", teams.ListMembers)
admin.POST("/teams/:id/members", teams.AddMember)
admin.GET("/presets", presets.ListAdminPersonas)
admin.POST("/presets", presets.CreateAdminPersona)
admin.GET("/presets/:id/knowledge-bases", presets.GetPersonaKBs) // v0.17.0
admin.PUT("/presets/:id/knowledge-bases", presets.SetPersonaKBs) // v0.17.0
admin.GET("/personas", personas.ListAdminPersonas)
admin.POST("/personas", personas.CreateAdminPersona)
admin.GET("/personas/:id/knowledge-bases", personas.GetPersonaKBs) // v0.17.0
admin.PUT("/personas/:id/knowledge-bases", personas.SetPersonaKBs) // v0.17.0
// Admin groups (v0.16.0)
groupAdm := NewGroupHandler(stores)
@@ -802,14 +802,14 @@ func TestIntegration_TeamMemberManagement(t *testing.T) {
}
}
// ── 7. Presets (Policy Gated) ───────────────
// ── 7. Personas (Policy Gated) ───────────────
func TestIntegration_PresetCreation_PolicyGated(t *testing.T) {
func TestIntegration_PersonaCreation_PolicyGated(t *testing.T) {
h := setupHarness(t)
adminID, adminToken := h.createAdminUser("admin", "admin@test.com")
_ = adminID
// Create a provider config (presets need a valid model reference)
// Create a provider config (personas need a valid model reference)
w := h.request("POST", "/api/v1/admin/configs", adminToken, map[string]interface{}{
"name": "TestProvider", "provider": "openai",
"endpoint": "https://api.openai.com/v1", "api_key": "sk-test",
@@ -821,13 +821,13 @@ func TestIntegration_PresetCreation_PolicyGated(t *testing.T) {
// Ensure allow_user_personas = false
database.TestDB.Exec("INSERT INTO platform_policies (key, value) VALUES ('allow_user_personas', 'false') ON CONFLICT (key) DO UPDATE SET value = 'false'")
// Try to create a preset as admin (role=admin but policy says no)
w = h.request("POST", "/api/v1/presets", adminToken, map[string]interface{}{
"name": "My Preset", "base_model_id": "gpt-4o",
// Try to create a persona as admin (role=admin but policy says no)
w = h.request("POST", "/api/v1/personas", adminToken, map[string]interface{}{
"name": "My Persona", "base_model_id": "gpt-4o",
"provider_config_id": configID, "system_prompt": "You are helpful",
})
if w.Code != http.StatusForbidden {
t.Fatalf("preset create with policy=false: want 403, got %d: %s", w.Code, w.Body.String())
t.Fatalf("persona create with policy=false: want 403, got %d: %s", w.Code, w.Body.String())
}
// Enable the policy
@@ -835,18 +835,18 @@ func TestIntegration_PresetCreation_PolicyGated(t *testing.T) {
map[string]interface{}{"value": "true"})
// Now creation should succeed
w = h.request("POST", "/api/v1/presets", adminToken, map[string]interface{}{
"name": "My Preset", "base_model_id": "gpt-4o",
w = h.request("POST", "/api/v1/personas", adminToken, map[string]interface{}{
"name": "My Persona", "base_model_id": "gpt-4o",
"provider_config_id": configID, "system_prompt": "You are helpful",
})
if w.Code != http.StatusCreated {
t.Fatalf("preset create with policy=true: want 201, got %d: %s", w.Code, w.Body.String())
t.Fatalf("persona create with policy=true: want 201, got %d: %s", w.Code, w.Body.String())
}
// List presets
w = h.request("GET", "/api/v1/presets", adminToken, nil)
// List personas
w = h.request("GET", "/api/v1/personas", adminToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("list presets: want 200, got %d: %s", w.Code, w.Body.String())
t.Fatalf("list personas: want 200, got %d: %s", w.Code, w.Body.String())
}
}
@@ -1105,7 +1105,7 @@ func TestIntegration_AdminModelFetchEnableUserSees(t *testing.T) {
t.Error("MISSING: model_id — required for composite ID")
}
// source required for preset detection
// source required for persona detection
if m["source"] == nil || m["source"] == "" {
t.Error("MISSING: source — frontend uses this to distinguish catalog vs persona")
}
@@ -2748,7 +2748,7 @@ func TestResourceGrants(t *testing.T) {
decode(w, &group)
// Create a persona (admin)
w = h.request("POST", "/api/v1/admin/presets", adminToken, map[string]interface{}{
w = h.request("POST", "/api/v1/admin/personas", adminToken, map[string]interface{}{
"name": "Test Bot",
"base_model_id": "test-model",
"scope": "global",
@@ -2840,11 +2840,11 @@ func TestGroupBasedPersonaAccess(t *testing.T) {
}
// ── User should NOT see team persona ──
w = h.request("GET", "/api/v1/presets", userToken, nil)
var presetsResp map[string]interface{}
decode(w, &presetsResp)
presets, _ := presetsResp["presets"].([]interface{})
for _, p := range presets {
w = h.request("GET", "/api/v1/personas", userToken, nil)
var personasResp map[string]interface{}
decode(w, &personasResp)
personas, _ := personasResp["personas"].([]interface{})
for _, p := range personas {
pm := p.(map[string]interface{})
if pm["id"] == personaID {
t.Fatalf("user should NOT see team persona without group access")
@@ -2872,14 +2872,14 @@ func TestGroupBasedPersonaAccess(t *testing.T) {
}
// ── User should NOW see team persona via group grant ──
w = h.request("GET", "/api/v1/presets", userToken, nil)
w = h.request("GET", "/api/v1/personas", userToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("list presets: want 200, got %d: %s", w.Code, w.Body.String())
t.Fatalf("list personas: want 200, got %d: %s", w.Code, w.Body.String())
}
decode(w, &presetsResp)
presets, _ = presetsResp["presets"].([]interface{})
decode(w, &personasResp)
personas, _ = personasResp["personas"].([]interface{})
found := false
for _, p := range presets {
for _, p := range personas {
pm := p.(map[string]interface{})
if pm["id"] == personaID {
found = true
@@ -2887,16 +2887,16 @@ func TestGroupBasedPersonaAccess(t *testing.T) {
}
}
if !found {
t.Fatalf("user should see team persona after group grant, but didn't find it in %d presets (response: %s)", len(presets), w.Body.String())
t.Fatalf("user should see team persona after group grant, but didn't find it in %d personas (response: %s)", len(personas), w.Body.String())
}
// ── Remove user from group → should lose access ──
w = h.request("DELETE", fmt.Sprintf("/api/v1/admin/groups/%s/members/%s", group.ID, userID), adminToken, nil)
w = h.request("GET", "/api/v1/presets", userToken, nil)
decode(w, &presetsResp)
presets, _ = presetsResp["presets"].([]interface{})
for _, p := range presets {
w = h.request("GET", "/api/v1/personas", userToken, nil)
decode(w, &personasResp)
personas, _ = personasResp["personas"].([]interface{})
for _, p := range personas {
pm := p.(map[string]interface{})
if pm["id"] == personaID {
t.Fatalf("user should NOT see persona after group removal")
@@ -2915,7 +2915,7 @@ func TestIntegration_PersonaKB_Binding(t *testing.T) {
_, adminToken := h.createAdminUser("admin", "admin@test.com")
// 1. Create a global persona via admin API
w := h.request("POST", "/api/v1/admin/presets", adminToken, map[string]interface{}{
w := h.request("POST", "/api/v1/admin/personas", adminToken, map[string]interface{}{
"name": "KB Persona",
"base_model_id": "test-model",
"system_prompt": "You are a test persona.",
@@ -2941,7 +2941,7 @@ func TestIntegration_PersonaKB_Binding(t *testing.T) {
// 3. Bind KB to persona
w = h.request("PUT",
fmt.Sprintf("/api/v1/admin/presets/%s/knowledge-bases", personaID),
fmt.Sprintf("/api/v1/admin/personas/%s/knowledge-bases", personaID),
adminToken, map[string]interface{}{
"kb_ids": []string{kbID},
"auto_search": map[string]bool{kbID: true},
@@ -2952,7 +2952,7 @@ func TestIntegration_PersonaKB_Binding(t *testing.T) {
// 4. Read back bindings
w = h.request("GET",
fmt.Sprintf("/api/v1/admin/presets/%s/knowledge-bases", personaID),
fmt.Sprintf("/api/v1/admin/personas/%s/knowledge-bases", personaID),
adminToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("get persona KBs: want 200, got %d: %s", w.Code, w.Body.String())
@@ -2973,7 +2973,7 @@ func TestIntegration_PersonaKB_Binding(t *testing.T) {
// 5. Unbind — send empty list
w = h.request("PUT",
fmt.Sprintf("/api/v1/admin/presets/%s/knowledge-bases", personaID),
fmt.Sprintf("/api/v1/admin/personas/%s/knowledge-bases", personaID),
adminToken, map[string]interface{}{
"kb_ids": []string{},
})
@@ -2983,7 +2983,7 @@ func TestIntegration_PersonaKB_Binding(t *testing.T) {
// 6. Verify empty
w = h.request("GET",
fmt.Sprintf("/api/v1/admin/presets/%s/knowledge-bases", personaID),
fmt.Sprintf("/api/v1/admin/personas/%s/knowledge-bases", personaID),
adminToken, nil)
if w.Code != http.StatusOK {
t.Fatalf("get empty persona KBs: want 200, got %d: %s", w.Code, w.Body.String())