Files
ragflow/internal/service/langfuse_service_test.go
Hz_ e290a0d23e feat(go-api): Langfuse API key migration behavior (#16356)
## Summary

- Align Langfuse API key set/get/delete behavior with the Python
implementation.
- Improve DAO handling for Langfuse credential save/delete flows.
- Add tests for Langfuse service error handling and API key lifecycle
behavior.
2026-06-25 19:25:55 +08:00

246 lines
8.7 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package service
import (
"context"
"errors"
"testing"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"ragflow/internal/common"
"ragflow/internal/dao"
"ragflow/internal/entity"
)
func setupLangfuseServiceTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
TranslateError: true,
})
if err != nil {
t.Fatalf("failed to open sqlite: %v", err)
}
if err := db.AutoMigrate(&entity.TenantLangfuse{}); err != nil {
t.Fatalf("failed to migrate test schema: %v", err)
}
origDB := dao.DB
dao.DB = db
t.Cleanup(func() { dao.DB = origDB })
return db
}
// stubLangfuseVerifier is a controllable langfuseVerifier for tests.
type stubLangfuseVerifier struct {
authOK bool
authErr error
projID string
projName string
projErr error
}
func (s stubLangfuseVerifier) AuthCheck(_ context.Context, _, _, _ string) (bool, error) {
return s.authOK, s.authErr
}
func (s stubLangfuseVerifier) GetProject(_ context.Context, _, _, _ string) (string, string, error) {
return s.projID, s.projName, s.projErr
}
func newLangfuseServiceForTest(v langfuseVerifier) *LangfuseService {
return &LangfuseService{langfuseDAO: dao.NewLangfuse(), verifier: v}
}
func TestLangfuseService_SetAPIKey_MissingFields(t *testing.T) {
setupLangfuseServiceTestDB(t)
svc := newLangfuseServiceForTest(stubLangfuseVerifier{authOK: true})
_, code, err := svc.SetAPIKey("tenant-1", "", "pk", "host")
if code != common.CodeDataError || err == nil || err.Error() != "Missing required fields" {
t.Fatalf("expected Missing required fields/CodeDataError, got code=%d err=%v", code, err)
}
}
func TestLangfuseService_SetAPIKey_InvalidKeys(t *testing.T) {
setupLangfuseServiceTestDB(t)
svc := newLangfuseServiceForTest(stubLangfuseVerifier{authOK: false})
_, code, err := svc.SetAPIKey("tenant-1", "sk", "pk", "host")
if code != common.CodeDataError || err == nil || err.Error() != "Invalid Langfuse keys" {
t.Fatalf("expected Invalid Langfuse keys/CodeDataError, got code=%d err=%v", code, err)
}
}
func TestLangfuseService_SetAPIKey_VerifierError(t *testing.T) {
setupLangfuseServiceTestDB(t)
svc := newLangfuseServiceForTest(stubLangfuseVerifier{authErr: errors.New("network down")})
_, code, err := svc.SetAPIKey("tenant-1", "sk", "pk", "host")
if code != common.CodeServerError || err == nil || err.Error() != "network down" {
t.Fatalf("expected verifier error/CodeServerError, got code=%d err=%v", code, err)
}
}
func TestLangfuseService_SetAPIKey_CreateThenUpdate(t *testing.T) {
db := setupLangfuseServiceTestDB(t)
svc := newLangfuseServiceForTest(stubLangfuseVerifier{authOK: true})
// Create
row, code, err := svc.SetAPIKey("tenant-1", "sk-1", "pk-1", "https://a.langfuse.com")
if err != nil || code != common.CodeSuccess {
t.Fatalf("create failed: code=%d err=%v", code, err)
}
if row.SecretKey != "sk-1" || row.Host != "https://a.langfuse.com" {
t.Fatalf("unexpected row: %+v", row)
}
var count int64
db.Model(&entity.TenantLangfuse{}).Where("tenant_id = ?", "tenant-1").Count(&count)
if count != 1 {
t.Fatalf("expected 1 row after create, got %d", count)
}
// Update (same tenant) should not create a second row
_, code, err = svc.SetAPIKey("tenant-1", "sk-2", "pk-2", "https://b.langfuse.com")
if err != nil || code != common.CodeSuccess {
t.Fatalf("update failed: code=%d err=%v", code, err)
}
db.Model(&entity.TenantLangfuse{}).Where("tenant_id = ?", "tenant-1").Count(&count)
if count != 1 {
t.Fatalf("expected still 1 row after update, got %d", count)
}
stored, _ := dao.NewLangfuse().GetByTenantID("tenant-1")
if stored == nil || stored.SecretKey != "sk-2" || stored.Host != "https://b.langfuse.com" {
t.Fatalf("update not persisted: %+v", stored)
}
}
func TestLangfuseService_GetAPIKey_NoRecord(t *testing.T) {
setupLangfuseServiceTestDB(t)
svc := newLangfuseServiceForTest(stubLangfuseVerifier{})
data, code, message, err := svc.GetAPIKey("tenant-1")
if err != nil || code != common.CodeSuccess || data != nil {
t.Fatalf("unexpected: code=%d data=%v err=%v", code, data, err)
}
if message != "Have not record any Langfuse keys." {
t.Fatalf("unexpected message: %q", message)
}
}
func TestLangfuseService_GetAPIKey_Unauthorized(t *testing.T) {
setupLangfuseServiceTestDB(t)
if err := dao.NewLangfuse().Create(&entity.TenantLangfuse{TenantID: "tenant-1", SecretKey: "sk", PublicKey: "pk", Host: "host"}); err != nil {
t.Fatalf("seed failed: %v", err)
}
svc := newLangfuseServiceForTest(stubLangfuseVerifier{projErr: ErrLangfuseUnauthorized})
data, code, message, err := svc.GetAPIKey("tenant-1")
if data != nil || code != common.CodeDataError || err == nil {
t.Fatalf("unexpected: code=%d data=%v err=%v", code, data, err)
}
if message != "Invalid Langfuse keys loaded" {
t.Fatalf("unexpected message: %q", message)
}
}
func TestLangfuseService_GetAPIKey_ApiError(t *testing.T) {
setupLangfuseServiceTestDB(t)
if err := dao.NewLangfuse().Create(&entity.TenantLangfuse{TenantID: "tenant-1", SecretKey: "sk", PublicKey: "pk", Host: "host"}); err != nil {
t.Fatalf("seed failed: %v", err)
}
svc := newLangfuseServiceForTest(stubLangfuseVerifier{projErr: &LangfuseAPIError{StatusCode: 500, Body: "boom"}})
data, code, message, err := svc.GetAPIKey("tenant-1")
if data != nil || code != common.CodeSuccess || err != nil {
t.Fatalf("unexpected: code=%d data=%v err=%v", code, data, err)
}
if message != "Error from Langfuse: langfuse: unexpected status 500: boom" {
t.Fatalf("unexpected message: %q", message)
}
}
func TestLangfuseService_GetAPIKey_NonAPIError(t *testing.T) {
setupLangfuseServiceTestDB(t)
if err := dao.NewLangfuse().Create(&entity.TenantLangfuse{TenantID: "tenant-1", SecretKey: "sk", PublicKey: "pk", Host: "host"}); err != nil {
t.Fatalf("seed failed: %v", err)
}
svc := newLangfuseServiceForTest(stubLangfuseVerifier{projErr: errors.New("json parse failed")})
data, code, message, err := svc.GetAPIKey("tenant-1")
if data != nil || code != common.CodeServerError || message != "" || err == nil {
t.Fatalf("unexpected: code=%d message=%q data=%v err=%v", code, message, data, err)
}
}
func TestLangfuseService_GetAPIKey_Success(t *testing.T) {
setupLangfuseServiceTestDB(t)
if err := dao.NewLangfuse().Create(&entity.TenantLangfuse{TenantID: "tenant-1", SecretKey: "sk", PublicKey: "pk", Host: "https://a.langfuse.com"}); err != nil {
t.Fatalf("seed failed: %v", err)
}
svc := newLangfuseServiceForTest(stubLangfuseVerifier{projID: "proj-1", projName: "My Project"})
data, code, message, err := svc.GetAPIKey("tenant-1")
if err != nil || code != common.CodeSuccess || message != "success" {
t.Fatalf("unexpected: code=%d message=%q err=%v", code, message, err)
}
if data == nil {
t.Fatalf("expected data, got nil")
}
if data.TenantID != "tenant-1" || data.Host != "https://a.langfuse.com" ||
data.SecretKey != "sk" || data.PublicKey != "pk" ||
data.ProjectID != "proj-1" || data.ProjectName != "My Project" {
t.Fatalf("unexpected data: %+v", data)
}
}
func TestLangfuseService_DeleteAPIKey_NoRecord(t *testing.T) {
setupLangfuseServiceTestDB(t)
svc := newLangfuseServiceForTest(stubLangfuseVerifier{})
ok, code, message, err := svc.DeleteAPIKey("tenant-1")
if ok || code != common.CodeSuccess || err != nil {
t.Fatalf("unexpected: ok=%v code=%d err=%v", ok, code, err)
}
if message != "Have not record any Langfuse keys." {
t.Fatalf("unexpected message: %q", message)
}
}
func TestLangfuseService_DeleteAPIKey_Success(t *testing.T) {
db := setupLangfuseServiceTestDB(t)
if err := dao.NewLangfuse().Create(&entity.TenantLangfuse{TenantID: "tenant-1", SecretKey: "sk", PublicKey: "pk", Host: "host"}); err != nil {
t.Fatalf("seed failed: %v", err)
}
svc := newLangfuseServiceForTest(stubLangfuseVerifier{})
ok, code, message, err := svc.DeleteAPIKey("tenant-1")
if !ok || code != common.CodeSuccess || message != "" || err != nil {
t.Fatalf("unexpected: ok=%v code=%d message=%q err=%v", ok, code, message, err)
}
var count int64
db.Model(&entity.TenantLangfuse{}).Where("tenant_id = ?", "tenant-1").Count(&count)
if count != 0 {
t.Fatalf("expected row deleted, count=%d", count)
}
}