feat(llm): implement scoped key management for providers
- Introduced ScopedKey function to generate unique provider keys based on owner type (user/team) to prevent key collisions. - Updated provider creation and management tests to utilize scoped keys, ensuring consistent key handling across different contexts. - Refactored existing tests to validate the correct generation and retrieval of scoped keys, enhancing overall provider management reliability. - Adjusted OpenAPI settings to reflect the use of scoped keys in provider operations, improving API consistency.
This commit is contained in:
parent
756ff95d3f
commit
a5ca482d7c
5 changed files with 129 additions and 47 deletions
|
|
@ -227,8 +227,9 @@ func TestGetMasked_EqualsGetDefault(t *testing.T) {
|
||||||
func TestListModels_ConnectorHasRealKey(t *testing.T) {
|
func TestListModels_ConnectorHasRealKey(t *testing.T) {
|
||||||
r := setupRegistry(t)
|
r := setupRegistry(t)
|
||||||
|
|
||||||
|
owner := llmprovider.ProviderOwner{Type: "user", UserID: "rk-user"}
|
||||||
p := llmprovider.Provider{
|
p := llmprovider.Provider{
|
||||||
Key: "realkey-prov",
|
Key: llmprovider.ScopedKey(&owner, "realkey-prov"),
|
||||||
Name: "RealKey Test",
|
Name: "RealKey Test",
|
||||||
Type: "openai",
|
Type: "openai",
|
||||||
APIURL: "https://api.openai.com",
|
APIURL: "https://api.openai.com",
|
||||||
|
|
@ -237,7 +238,7 @@ func TestListModels_ConnectorHasRealKey(t *testing.T) {
|
||||||
{ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"streaming"}, Enabled: true},
|
{ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"streaming"}, Enabled: true},
|
||||||
},
|
},
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Owner: llmprovider.ProviderOwner{Type: "user", UserID: "rk-user"},
|
Owner: owner,
|
||||||
}
|
}
|
||||||
_, err := r.Create(&p)
|
_, err := r.Create(&p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -290,13 +291,14 @@ func TestGetLazy(t *testing.T) {
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
r := setupRegistry(t)
|
r := setupRegistry(t)
|
||||||
|
|
||||||
|
p2Owner := llmprovider.ProviderOwner{Type: "user", UserID: "123"}
|
||||||
providers := []llmprovider.Provider{
|
providers := []llmprovider.Provider{
|
||||||
{Key: "p1", Name: "Provider 1", Type: "openai", Enabled: true,
|
{Key: "p1", Name: "Provider 1", Type: "openai", Enabled: true,
|
||||||
Models: []llmprovider.ModelInfo{{ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"vision", "tool_calls"}, Enabled: true}},
|
Models: []llmprovider.ModelInfo{{ID: "gpt-4o", Name: "GPT-4o", Capabilities: []string{"vision", "tool_calls"}, Enabled: true}},
|
||||||
Owner: llmprovider.ProviderOwner{Type: "system"}},
|
Owner: llmprovider.ProviderOwner{Type: "system"}},
|
||||||
{Key: "p2", Name: "Provider 2", Type: "anthropic", Enabled: false,
|
{Key: llmprovider.ScopedKey(&p2Owner, "p2"), Name: "Provider 2", Type: "anthropic", Enabled: false,
|
||||||
Models: []llmprovider.ModelInfo{{ID: "claude-3", Name: "Claude 3", Capabilities: []string{"tool_calls"}, Enabled: true}},
|
Models: []llmprovider.ModelInfo{{ID: "claude-3", Name: "Claude 3", Capabilities: []string{"tool_calls"}, Enabled: true}},
|
||||||
Owner: llmprovider.ProviderOwner{Type: "user", UserID: "123"}},
|
Owner: p2Owner},
|
||||||
{Key: "p3", Name: "Provider 3", Type: "openai", Enabled: true,
|
{Key: "p3", Name: "Provider 3", Type: "openai", Enabled: true,
|
||||||
Models: []llmprovider.ModelInfo{{ID: "gpt-4o-mini", Name: "GPT-4o Mini", Capabilities: []string{"streaming"}, Enabled: true}},
|
Models: []llmprovider.ModelInfo{{ID: "gpt-4o-mini", Name: "GPT-4o Mini", Capabilities: []string{"streaming"}, Enabled: true}},
|
||||||
Owner: llmprovider.ProviderOwner{Type: "system"}},
|
Owner: llmprovider.ProviderOwner{Type: "system"}},
|
||||||
|
|
@ -378,7 +380,7 @@ func TestList(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
found := false
|
found := false
|
||||||
for _, p := range list {
|
for _, p := range list {
|
||||||
if p.Key == "p2" {
|
if p.Name == "Provider 2" {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,63 @@ import (
|
||||||
"github.com/yaoapp/yao/setting"
|
"github.com/yaoapp/yao/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ScopedKey
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func TestScopedKeyFormats(t *testing.T) {
|
||||||
|
assert.Equal(t, "ualice.deepseek", llmprovider.ScopedKey(
|
||||||
|
&llmprovider.ProviderOwner{Type: "user", UserID: "alice"}, "deepseek"))
|
||||||
|
assert.Equal(t, "t9253.deepseek", llmprovider.ScopedKey(
|
||||||
|
&llmprovider.ProviderOwner{Type: "team", TeamID: "9253"}, "deepseek"))
|
||||||
|
assert.Equal(t, "deepseek", llmprovider.ScopedKey(
|
||||||
|
&llmprovider.ProviderOwner{Type: "system"}, "deepseek"))
|
||||||
|
assert.Equal(t, "deepseek", llmprovider.ScopedKey(
|
||||||
|
&llmprovider.ProviderOwner{}, "deepseek"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDifferentOwnerSameBaseKey(t *testing.T) {
|
||||||
|
r := setupRegistryWithSetting(t)
|
||||||
|
|
||||||
|
ownerA := llmprovider.ProviderOwner{Type: "team", TeamID: "teamA"}
|
||||||
|
ownerB := llmprovider.ProviderOwner{Type: "team", TeamID: "teamB"}
|
||||||
|
|
||||||
|
pA := createOwnedProvider(t, r, "deepseek", ownerA)
|
||||||
|
pB := createOwnedProvider(t, r, "deepseek", ownerB)
|
||||||
|
|
||||||
|
assert.Equal(t, "tteamA.deepseek", pA.Key)
|
||||||
|
assert.Equal(t, "tteamB.deepseek", pB.Key)
|
||||||
|
|
||||||
|
gotA, err := r.Get(pA.Key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, pA.Key, gotA.Key)
|
||||||
|
|
||||||
|
gotB, err := r.Get(pB.Key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, pB.Key, gotB.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSameOwnerDuplicateKey(t *testing.T) {
|
||||||
|
r := setupRegistryWithSetting(t)
|
||||||
|
|
||||||
|
owner := llmprovider.ProviderOwner{Type: "user", UserID: "u1"}
|
||||||
|
_ = createOwnedProvider(t, r, "openai", owner)
|
||||||
|
|
||||||
|
dup := llmprovider.Provider{
|
||||||
|
Key: llmprovider.ScopedKey(&owner, "openai"),
|
||||||
|
Name: "Dup",
|
||||||
|
Type: "openai",
|
||||||
|
APIURL: "https://api.openai.com",
|
||||||
|
APIKey: "sk-dup",
|
||||||
|
Enabled: true,
|
||||||
|
Models: []llmprovider.ModelInfo{{ID: "gpt-4o", Name: "GPT-4o", Enabled: true}},
|
||||||
|
Owner: owner,
|
||||||
|
}
|
||||||
|
_, err := r.Create(&dup)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "already exists")
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Identity interface
|
// Identity interface
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -107,8 +164,9 @@ func TestListModelsBy_UserFallback(t *testing.T) {
|
||||||
func TestListModelsExpandsMultipleModels(t *testing.T) {
|
func TestListModelsExpandsMultipleModels(t *testing.T) {
|
||||||
r := setupRegistryWithSetting(t)
|
r := setupRegistryWithSetting(t)
|
||||||
|
|
||||||
|
owner := llmprovider.ProviderOwner{Type: "user", UserID: "multi-u"}
|
||||||
p := llmprovider.Provider{
|
p := llmprovider.Provider{
|
||||||
Key: "multi-model-prov",
|
Key: llmprovider.ScopedKey(&owner, "multi-model-prov"),
|
||||||
Name: "MultiModel",
|
Name: "MultiModel",
|
||||||
Type: "openai",
|
Type: "openai",
|
||||||
APIURL: "https://api.openai.com",
|
APIURL: "https://api.openai.com",
|
||||||
|
|
@ -119,7 +177,7 @@ func TestListModelsExpandsMultipleModels(t *testing.T) {
|
||||||
{ID: "gpt-disabled", Name: "Disabled", Enabled: false},
|
{ID: "gpt-disabled", Name: "Disabled", Enabled: false},
|
||||||
},
|
},
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Owner: llmprovider.ProviderOwner{Type: "user", UserID: "multi-u"},
|
Owner: owner,
|
||||||
}
|
}
|
||||||
_, err := r.Create(&p)
|
_, err := r.Create(&p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -143,8 +201,9 @@ func TestListModelsExpandsMultipleModels(t *testing.T) {
|
||||||
func TestGetModelWithModelLevelCID(t *testing.T) {
|
func TestGetModelWithModelLevelCID(t *testing.T) {
|
||||||
r := setupRegistryWithSetting(t)
|
r := setupRegistryWithSetting(t)
|
||||||
|
|
||||||
|
owner := llmprovider.ProviderOwner{Type: "team", TeamID: "mlcid-t1"}
|
||||||
p := llmprovider.Provider{
|
p := llmprovider.Provider{
|
||||||
Key: "mlcid-prov",
|
Key: llmprovider.ScopedKey(&owner, "mlcid-prov"),
|
||||||
Name: "MLTest",
|
Name: "MLTest",
|
||||||
Type: "openai",
|
Type: "openai",
|
||||||
APIURL: "https://api.openai.com",
|
APIURL: "https://api.openai.com",
|
||||||
|
|
@ -154,7 +213,7 @@ func TestGetModelWithModelLevelCID(t *testing.T) {
|
||||||
{ID: "gpt-4o-mini", Name: "GPT-4o Mini", Enabled: true},
|
{ID: "gpt-4o-mini", Name: "GPT-4o Mini", Enabled: true},
|
||||||
},
|
},
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Owner: llmprovider.ProviderOwner{Type: "team", TeamID: "mlcid-t1"},
|
Owner: owner,
|
||||||
}
|
}
|
||||||
created, err := r.Create(&p)
|
created, err := r.Create(&p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -186,7 +245,7 @@ func TestGetModelByConnectorIDReverseLookup(t *testing.T) {
|
||||||
|
|
||||||
p := createOwnedProvider(t, r, "rev-prov", llmprovider.ProviderOwner{Type: "user", UserID: "u99"})
|
p := createOwnedProvider(t, r, "rev-prov", llmprovider.ProviderOwner{Type: "user", UserID: "u99"})
|
||||||
cid := p.ConnectorID
|
cid := p.ConnectorID
|
||||||
assert.NotEqual(t, p.Key, cid, "dynamic provider ConnectorID should differ from Key")
|
assert.Equal(t, p.Key, cid, "dynamic provider ConnectorID should equal scoped Key")
|
||||||
|
|
||||||
_ = connector.Unregister(cid)
|
_ = connector.Unregister(cid)
|
||||||
|
|
||||||
|
|
@ -365,11 +424,11 @@ func TestGetRoleCapabilitiesBy(t *testing.T) {
|
||||||
// helpers
|
// helpers
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func createOwnedProvider(t *testing.T, r *llmprovider.Registry, key string, owner llmprovider.ProviderOwner) *llmprovider.Provider {
|
func createOwnedProvider(t *testing.T, r *llmprovider.Registry, baseKey string, owner llmprovider.ProviderOwner) *llmprovider.Provider {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
p := llmprovider.Provider{
|
p := llmprovider.Provider{
|
||||||
Key: key,
|
Key: llmprovider.ScopedKey(&owner, baseKey),
|
||||||
Name: "Test " + key,
|
Name: "Test " + baseKey,
|
||||||
Type: "openai",
|
Type: "openai",
|
||||||
APIURL: "https://api.openai.com",
|
APIURL: "https://api.openai.com",
|
||||||
APIKey: "sk-test-owned",
|
APIKey: "sk-test-owned",
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,29 @@ import (
|
||||||
goullm "github.com/yaoapp/gou/llm"
|
goullm "github.com/yaoapp/gou/llm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ScopedKey returns a provider key prefixed with the owner scope.
|
||||||
|
// This ensures unique keys per user/team in the store.
|
||||||
|
//
|
||||||
|
// user -> "u<userID>.<baseKey>"
|
||||||
|
// team -> "t<teamID>.<baseKey>"
|
||||||
|
// other -> baseKey (unchanged)
|
||||||
|
func ScopedKey(owner *ProviderOwner, baseKey string) string {
|
||||||
|
switch owner.Type {
|
||||||
|
case "user":
|
||||||
|
return "u" + owner.UserID + "." + baseKey
|
||||||
|
case "team":
|
||||||
|
return "t" + owner.TeamID + "." + baseKey
|
||||||
|
default:
|
||||||
|
return baseKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// connectorID builds the runtime ID for registering into connector.Connectors.
|
// connectorID builds the runtime ID for registering into connector.Connectors.
|
||||||
// Dynamic providers get an owner prefix to avoid collision with builtin IDs.
|
// For user/team providers the Key is already scoped, so use it directly.
|
||||||
func connectorID(p *Provider) string {
|
func connectorID(p *Provider) string {
|
||||||
switch p.Owner.Type {
|
switch p.Owner.Type {
|
||||||
case "user":
|
case "user", "team":
|
||||||
return "u" + p.Owner.UserID + "." + p.Key
|
return p.Key
|
||||||
case "team":
|
|
||||||
return "t" + p.Owner.TeamID + "." + p.Key
|
|
||||||
default:
|
default:
|
||||||
return "s." + p.Key
|
return "s." + p.Key
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -597,7 +597,7 @@ func handleLLMProviderCreate(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.Key = presetKey
|
provider.Key = llmprovider.ScopedKey(owner, presetKey)
|
||||||
provider.Name = preset.Name
|
provider.Name = preset.Name
|
||||||
provider.Type = preset.Type
|
provider.Type = preset.Type
|
||||||
provider.APIURL = preset.APIURL
|
provider.APIURL = preset.APIURL
|
||||||
|
|
@ -650,7 +650,7 @@ func handleLLMProviderCreate(c *gin.Context) {
|
||||||
respondError(c, http.StatusBadRequest, "key is required for custom provider")
|
respondError(c, http.StatusBadRequest, "key is required for custom provider")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
provider.Key = key
|
provider.Key = llmprovider.ScopedKey(owner, key)
|
||||||
|
|
||||||
name, _ := body["name"].(string)
|
name, _ := body["name"].(string)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
|
|
||||||
|
|
@ -90,19 +90,21 @@ func llmBody(t *testing.T, resp *http.Response) map[string]interface{} {
|
||||||
return body
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestOpenAI(t *testing.T, serverURL, token string) {
|
func createTestOpenAI(t *testing.T, serverURL, token string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
apiKey := requireOpenAIKey(t)
|
apiKey := requireOpenAIKey(t)
|
||||||
llmprovider.Global.Delete("openai")
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"preset_key": "openai",
|
"preset_key": "openai",
|
||||||
"api_key": apiKey,
|
"api_key": apiKey,
|
||||||
"model_ids": []string{"gpt-4o", "gpt-4o-mini"},
|
"model_ids": []string{"gpt-4o", "gpt-4o-mini"},
|
||||||
}
|
}
|
||||||
resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload)
|
resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload)
|
||||||
resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusCreated, resp.StatusCode, "createTestOpenAI should succeed")
|
assert.Equal(t, http.StatusCreated, resp.StatusCode, "createTestOpenAI should succeed")
|
||||||
t.Cleanup(func() { llmprovider.Global.Delete("openai") })
|
body := llmBody(t, resp)
|
||||||
|
scopedKey, _ := body["key"].(string)
|
||||||
|
t.Cleanup(func() { llmprovider.Global.Delete(scopedKey) })
|
||||||
|
return scopedKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------- Functional tests -----------
|
// ----------- Functional tests -----------
|
||||||
|
|
@ -162,7 +164,6 @@ func TestLLMProviderCreate(t *testing.T) {
|
||||||
initSettingRegistry(t)
|
initSettingRegistry(t)
|
||||||
initLLMRegistry(t)
|
initLLMRegistry(t)
|
||||||
token := obtainToken(t, serverURL)
|
token := obtainToken(t, serverURL)
|
||||||
llmprovider.Global.Delete("openai")
|
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"preset_key": "openai",
|
"preset_key": "openai",
|
||||||
|
|
@ -172,10 +173,12 @@ func TestLLMProviderCreate(t *testing.T) {
|
||||||
resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload)
|
resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||||
t.Cleanup(func() { llmprovider.Global.Delete("openai") })
|
|
||||||
|
|
||||||
body := llmBody(t, resp)
|
body := llmBody(t, resp)
|
||||||
assert.Equal(t, "openai", body["key"])
|
scopedKey, _ := body["key"].(string)
|
||||||
|
t.Cleanup(func() { llmprovider.Global.Delete(scopedKey) })
|
||||||
|
|
||||||
|
assert.Contains(t, scopedKey, ".openai", "scoped key should end with .openai")
|
||||||
assert.Equal(t, "OpenAI", body["name"])
|
assert.Equal(t, "OpenAI", body["name"])
|
||||||
assert.Equal(t, "openai", body["type"])
|
assert.Equal(t, "openai", body["type"])
|
||||||
|
|
||||||
|
|
@ -199,7 +202,6 @@ func TestLLMProviderCreateCustom(t *testing.T) {
|
||||||
initSettingRegistry(t)
|
initSettingRegistry(t)
|
||||||
initLLMRegistry(t)
|
initLLMRegistry(t)
|
||||||
token := obtainToken(t, serverURL)
|
token := obtainToken(t, serverURL)
|
||||||
llmprovider.Global.Delete("my-custom-llm")
|
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"key": "my-custom-llm",
|
"key": "my-custom-llm",
|
||||||
|
|
@ -215,10 +217,12 @@ func TestLLMProviderCreateCustom(t *testing.T) {
|
||||||
resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload)
|
resp := llmPost(t, llmURL(serverURL, "/providers"), token, payload)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||||
t.Cleanup(func() { llmprovider.Global.Delete("my-custom-llm") })
|
|
||||||
|
|
||||||
body := llmBody(t, resp)
|
body := llmBody(t, resp)
|
||||||
assert.Equal(t, "my-custom-llm", body["key"])
|
scopedKey, _ := body["key"].(string)
|
||||||
|
t.Cleanup(func() { llmprovider.Global.Delete(scopedKey) })
|
||||||
|
|
||||||
|
assert.Contains(t, scopedKey, ".my-custom-llm", "scoped key should end with .my-custom-llm")
|
||||||
assert.Equal(t, "My Custom LLM", body["name"])
|
assert.Equal(t, "My Custom LLM", body["name"])
|
||||||
assert.Equal(t, true, body["is_custom"])
|
assert.Equal(t, true, body["is_custom"])
|
||||||
|
|
||||||
|
|
@ -233,7 +237,7 @@ func TestLLMProviderUpdate(t *testing.T) {
|
||||||
initLLMRegistry(t)
|
initLLMRegistry(t)
|
||||||
token := obtainToken(t, serverURL)
|
token := obtainToken(t, serverURL)
|
||||||
|
|
||||||
createTestOpenAI(t, serverURL, token)
|
scopedKey := createTestOpenAI(t, serverURL, token)
|
||||||
|
|
||||||
updatePayload := map[string]interface{}{
|
updatePayload := map[string]interface{}{
|
||||||
"name": "Updated OpenAI",
|
"name": "Updated OpenAI",
|
||||||
|
|
@ -242,7 +246,7 @@ func TestLLMProviderUpdate(t *testing.T) {
|
||||||
{"id": "gpt-4o", "name": "GPT-4o Updated", "capabilities": []string{"vision", "tool_calls"}, "enabled": true},
|
{"id": "gpt-4o", "name": "GPT-4o Updated", "capabilities": []string{"vision", "tool_calls"}, "enabled": true},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
resp := llmPut(t, llmURL(serverURL, "/providers/openai"), token, updatePayload)
|
resp := llmPut(t, llmURL(serverURL, "/providers/"+scopedKey), token, updatePayload)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
|
@ -265,19 +269,20 @@ func TestLLMProviderDelete(t *testing.T) {
|
||||||
initSettingRegistry(t)
|
initSettingRegistry(t)
|
||||||
initLLMRegistry(t)
|
initLLMRegistry(t)
|
||||||
token := obtainToken(t, serverURL)
|
token := obtainToken(t, serverURL)
|
||||||
llmprovider.Global.Delete("anthropic")
|
|
||||||
|
|
||||||
createPayload := map[string]interface{}{
|
createPayload := map[string]interface{}{
|
||||||
"preset_key": "anthropic",
|
"preset_key": "anthropic",
|
||||||
"api_key": anthropicKey,
|
"api_key": anthropicKey,
|
||||||
}
|
}
|
||||||
createResp := llmPost(t, llmURL(serverURL, "/providers"), token, createPayload)
|
createResp := llmPost(t, llmURL(serverURL, "/providers"), token, createPayload)
|
||||||
|
createBody := llmBody(t, createResp)
|
||||||
createResp.Body.Close()
|
createResp.Body.Close()
|
||||||
assert.Equal(t, http.StatusCreated, createResp.StatusCode)
|
assert.Equal(t, http.StatusCreated, createResp.StatusCode)
|
||||||
|
scopedKey, _ := createBody["key"].(string)
|
||||||
|
|
||||||
rolesPayload := map[string]interface{}{
|
rolesPayload := map[string]interface{}{
|
||||||
"default": map[string]interface{}{
|
"default": map[string]interface{}{
|
||||||
"provider": "anthropic",
|
"provider": scopedKey,
|
||||||
"model": "claude-sonnet-4-6",
|
"model": "claude-sonnet-4-6",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -285,7 +290,7 @@ func TestLLMProviderDelete(t *testing.T) {
|
||||||
rolesResp.Body.Close()
|
rolesResp.Body.Close()
|
||||||
assert.Equal(t, http.StatusOK, rolesResp.StatusCode)
|
assert.Equal(t, http.StatusOK, rolesResp.StatusCode)
|
||||||
|
|
||||||
deleteResp := llmDelete(t, llmURL(serverURL, "/providers/anthropic"), token)
|
deleteResp := llmDelete(t, llmURL(serverURL, "/providers/"+scopedKey), token)
|
||||||
defer deleteResp.Body.Close()
|
defer deleteResp.Body.Close()
|
||||||
assert.Equal(t, http.StatusOK, deleteResp.StatusCode)
|
assert.Equal(t, http.StatusOK, deleteResp.StatusCode)
|
||||||
|
|
||||||
|
|
@ -307,21 +312,22 @@ func TestLLMProviderDeleteForbidden(t *testing.T) {
|
||||||
initLLMRegistry(t)
|
initLLMRegistry(t)
|
||||||
token := obtainToken(t, serverURL)
|
token := obtainToken(t, serverURL)
|
||||||
|
|
||||||
llmprovider.Global.Delete("other-team-provider")
|
otherOwner := llmprovider.ProviderOwner{Type: "user", UserID: "some-other-user-999"}
|
||||||
|
scopedKey := llmprovider.ScopedKey(&otherOwner, "other-team-provider")
|
||||||
otherProvider := &llmprovider.Provider{
|
otherProvider := &llmprovider.Provider{
|
||||||
Key: "other-team-provider",
|
Key: scopedKey,
|
||||||
Name: "Other Team's Provider",
|
Name: "Other Team's Provider",
|
||||||
Type: "openai",
|
Type: "openai",
|
||||||
APIURL: "https://api.example.com",
|
APIURL: "https://api.example.com",
|
||||||
Models: []llmprovider.ModelInfo{},
|
Models: []llmprovider.ModelInfo{},
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Source: llmprovider.ProviderSourceDynamic,
|
Source: llmprovider.ProviderSourceDynamic,
|
||||||
Owner: llmprovider.ProviderOwner{Type: "user", UserID: "some-other-user-999"},
|
Owner: otherOwner,
|
||||||
}
|
}
|
||||||
llmprovider.Global.Create(otherProvider)
|
llmprovider.Global.Create(otherProvider)
|
||||||
t.Cleanup(func() { llmprovider.Global.Delete("other-team-provider") })
|
t.Cleanup(func() { llmprovider.Global.Delete(scopedKey) })
|
||||||
|
|
||||||
resp := llmDelete(t, llmURL(serverURL, "/providers/other-team-provider"), token)
|
resp := llmDelete(t, llmURL(serverURL, "/providers/"+scopedKey), token)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should not be able to delete another user's provider")
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode, "should not be able to delete another user's provider")
|
||||||
}
|
}
|
||||||
|
|
@ -333,9 +339,9 @@ func TestLLMProviderTest(t *testing.T) {
|
||||||
initLLMRegistry(t)
|
initLLMRegistry(t)
|
||||||
token := obtainToken(t, serverURL)
|
token := obtainToken(t, serverURL)
|
||||||
|
|
||||||
createTestOpenAI(t, serverURL, token)
|
scopedKey := createTestOpenAI(t, serverURL, token)
|
||||||
|
|
||||||
resp := llmPost(t, llmURL(serverURL, "/providers/openai/test"), token, nil)
|
resp := llmPost(t, llmURL(serverURL, "/providers/"+scopedKey+"/test"), token, nil)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
|
@ -351,15 +357,15 @@ func TestLLMRoles(t *testing.T) {
|
||||||
initSettingRegistry(t)
|
initSettingRegistry(t)
|
||||||
initLLMRegistry(t)
|
initLLMRegistry(t)
|
||||||
token := obtainToken(t, serverURL)
|
token := obtainToken(t, serverURL)
|
||||||
createTestOpenAI(t, serverURL, token)
|
scopedKey := createTestOpenAI(t, serverURL, token)
|
||||||
|
|
||||||
rolesPayload := map[string]interface{}{
|
rolesPayload := map[string]interface{}{
|
||||||
"default": map[string]interface{}{
|
"default": map[string]interface{}{
|
||||||
"provider": "openai",
|
"provider": scopedKey,
|
||||||
"model": "gpt-4o",
|
"model": "gpt-4o",
|
||||||
},
|
},
|
||||||
"vision": map[string]interface{}{
|
"vision": map[string]interface{}{
|
||||||
"provider": "openai",
|
"provider": scopedKey,
|
||||||
"model": "gpt-4o",
|
"model": "gpt-4o",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -404,11 +410,11 @@ func TestLLMRolesValidation(t *testing.T) {
|
||||||
defer resp2.Body.Close()
|
defer resp2.Body.Close()
|
||||||
assert.Equal(t, http.StatusBadRequest, resp2.StatusCode, "should reject non-existent provider")
|
assert.Equal(t, http.StatusBadRequest, resp2.StatusCode, "should reject non-existent provider")
|
||||||
|
|
||||||
createTestOpenAI(t, serverURL, token)
|
scopedKey := createTestOpenAI(t, serverURL, token)
|
||||||
|
|
||||||
resp3 := llmPut(t, llmURL(serverURL, "/roles"), token, map[string]interface{}{
|
resp3 := llmPut(t, llmURL(serverURL, "/roles"), token, map[string]interface{}{
|
||||||
"default": map[string]interface{}{
|
"default": map[string]interface{}{
|
||||||
"provider": "openai",
|
"provider": scopedKey,
|
||||||
"model": "nonexistent-model",
|
"model": "nonexistent-model",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue