Files
ragflow/internal/handler/providers_test.go
bitloi ea3a5dba11 fix: validate custom model inputs (#15200)
### What problem does this PR solve?

Closes #15199.

The add-custom-model endpoint is routed through
`/api/v1/providers/:provider_name/instances/:instance_name/models`, but
the handler previously trusted `provider_name` and `instance_name` from
the JSON body instead of the path target. A request could therefore hit
one provider/instance URL while operating on a different body
provider/instance.

The same handler only rejected `model_types` when the slice was nil. An
empty array passed validation and reached
`ModelProviderService.AddCustomModel`, where `request.ModelTypes[0]`
could panic.

This PR makes the path provider/instance authoritative, rejects
mismatched body values, rejects missing or empty `model_types`, and adds
a service-level guard so direct service callers cannot hit the same
panic path.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2026-05-29 10:15:01 +08:00

112 lines
2.8 KiB
Go

package handler
import (
"testing"
"ragflow/internal/service"
)
func TestPrepareAddCustomModelRequestUsesPathTarget(t *testing.T) {
req := service.AddCustomModelRequest{
ModelName: "custom-chat",
ModelTypes: []string{"chat"},
}
if err := prepareAddCustomModelRequest(&req, "openai", "default"); err != nil {
t.Fatalf("prepareAddCustomModelRequest returned error: %v", err)
}
if req.ProviderName != "openai" {
t.Fatalf("expected provider name from path, got %q", req.ProviderName)
}
if req.InstanceName != "default" {
t.Fatalf("expected instance name from path, got %q", req.InstanceName)
}
}
func TestPrepareAddCustomModelRequestAcceptsCaseInsensitivePathMatch(t *testing.T) {
req := service.AddCustomModelRequest{
ProviderName: "openai",
InstanceName: "default",
ModelName: "custom-chat",
ModelTypes: []string{"chat"},
}
if err := prepareAddCustomModelRequest(&req, "OpenAI", "Default"); err != nil {
t.Fatalf("prepareAddCustomModelRequest returned error: %v", err)
}
if req.ProviderName != "OpenAI" {
t.Fatalf("expected provider name from path, got %q", req.ProviderName)
}
if req.InstanceName != "Default" {
t.Fatalf("expected instance name from path, got %q", req.InstanceName)
}
}
func TestPrepareAddCustomModelRequestRejectsPathMismatches(t *testing.T) {
tests := []struct {
name string
req service.AddCustomModelRequest
expectedErr string
}{
{
name: "provider",
req: service.AddCustomModelRequest{
ProviderName: "deepseek",
InstanceName: "default",
ModelName: "custom-chat",
ModelTypes: []string{"chat"},
},
expectedErr: "Provider name does not match path",
},
{
name: "instance",
req: service.AddCustomModelRequest{
ProviderName: "openai",
InstanceName: "other",
ModelName: "custom-chat",
ModelTypes: []string{"chat"},
},
expectedErr: "Instance name does not match path",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := prepareAddCustomModelRequest(&tt.req, "openai", "default")
if err == nil {
t.Fatal("expected mismatch error")
}
if err.Error() != tt.expectedErr {
t.Fatalf("expected %q, got %q", tt.expectedErr, err.Error())
}
})
}
}
func TestPrepareAddCustomModelRequestRejectsEmptyModelTypes(t *testing.T) {
tests := []struct {
name string
modelTypes []string
}{
{name: "nil"},
{name: "empty", modelTypes: []string{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := service.AddCustomModelRequest{
ModelName: "custom-chat",
ModelTypes: tt.modelTypes,
}
err := prepareAddCustomModelRequest(&req, "openai", "default")
if err == nil {
t.Fatal("expected empty model_types to return an error")
}
if err.Error() != "Model type is required" {
t.Fatalf("expected model type error, got %q", err.Error())
}
})
}
}