mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-01 00:05:43 +08:00
## Summary Implement the `GET /api/v1/agents/<agent_id>/versions/<version_id>` endpoint in Go, returning full version details including DSL. Depends on #15629 which introduced the version list endpoint and `UserCanvasVersionDAO` infrastructure. ### Changes - **Modified**: `internal/handler/agent.go` — Added `GetAgentVersion` handler with auth check and ownership verification - **Modified**: `internal/router/router.go` — Registered `GET /:agent_id/versions/:version_id` route - **New/Modified tests**: Service and handler tests for the version detail endpoint ### Testing ``` === RUN TestGetVersion_Success --- PASS === RUN TestGetVersion_WrongCanvas --- PASS === RUN TestGetVersion_NotFound --- PASS === RUN TestGetAgentVersionHandler_Success --- PASS === RUN TestGetAgentVersionHandler_VersionNotFound --- PASS ``` Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
348 lines
9.2 KiB
Go
348 lines
9.2 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 (
|
|
"testing"
|
|
"time"
|
|
|
|
"ragflow/internal/dao"
|
|
"ragflow/internal/entity"
|
|
)
|
|
|
|
// TestListVersions_Success verifies that ListVersions returns all versions
|
|
// for a canvas, ordered by update_time DESC.
|
|
func TestListVersions_Success(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
// Migrate tables needed for agent versions
|
|
if err := testDB.AutoMigrate(
|
|
&entity.User{},
|
|
&entity.UserCanvas{},
|
|
&entity.UserCanvasVersion{},
|
|
&entity.UserTenant{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
now := time.Now()
|
|
|
|
// Insert canvas owner
|
|
testDB.Create(&entity.User{ID: "user-1", Nickname: "owner", Email: "owner@test.com"})
|
|
|
|
// Insert canvas
|
|
testDB.Create(&entity.UserCanvas{
|
|
ID: "canvas-1",
|
|
UserID: "user-1",
|
|
Title: sptr("Test Agent"),
|
|
})
|
|
|
|
// Insert 3 versions with staggered timestamps
|
|
testDB.Create(&entity.UserCanvasVersion{
|
|
ID: "v1",
|
|
UserCanvasID: "canvas-1",
|
|
Title: sptr("v1_oldest"),
|
|
BaseModel: entity.BaseModel{
|
|
CreateTime: ptr(now.Add(-2 * time.Hour).UnixMilli()),
|
|
UpdateTime: ptr(now.Add(-2 * time.Hour).UnixMilli()),
|
|
},
|
|
})
|
|
testDB.Create(&entity.UserCanvasVersion{
|
|
ID: "v2",
|
|
UserCanvasID: "canvas-1",
|
|
Title: sptr("v2_middle"),
|
|
BaseModel: entity.BaseModel{
|
|
CreateTime: ptr(now.Add(-1 * time.Hour).UnixMilli()),
|
|
UpdateTime: ptr(now.Add(-1 * time.Hour).UnixMilli()),
|
|
},
|
|
})
|
|
testDB.Create(&entity.UserCanvasVersion{
|
|
ID: "v3",
|
|
UserCanvasID: "canvas-1",
|
|
Title: sptr("v3_newest"),
|
|
BaseModel: entity.BaseModel{
|
|
CreateTime: ptr(now.UnixMilli()),
|
|
UpdateTime: ptr(now.UnixMilli()),
|
|
},
|
|
})
|
|
|
|
svc := NewAgentService()
|
|
versions, err := svc.ListVersions("canvas-1")
|
|
if err != nil {
|
|
t.Fatalf("ListVersions failed: %v", err)
|
|
}
|
|
if len(versions) != 3 {
|
|
t.Fatalf("expected 3 versions, got %d", len(versions))
|
|
}
|
|
// Verify DESC order
|
|
if *versions[0].Title != "v3_newest" {
|
|
t.Errorf("expected v3_newest first, got %s", *versions[0].Title)
|
|
}
|
|
if *versions[1].Title != "v2_middle" {
|
|
t.Errorf("expected v2_middle second, got %s", *versions[1].Title)
|
|
}
|
|
if *versions[2].Title != "v1_oldest" {
|
|
t.Errorf("expected v1_oldest last, got %s", *versions[2].Title)
|
|
}
|
|
}
|
|
|
|
// TestListVersions_Empty verifies that ListVersions returns an empty slice
|
|
// when no versions exist.
|
|
func TestListVersions_Empty(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
if err := testDB.AutoMigrate(
|
|
&entity.UserCanvas{},
|
|
&entity.UserCanvasVersion{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
// Insert canvas with no versions
|
|
testDB.Create(&entity.UserCanvas{
|
|
ID: "canvas-empty",
|
|
UserID: "user-1",
|
|
Title: sptr("Empty Agent"),
|
|
})
|
|
|
|
svc := NewAgentService()
|
|
versions, err := svc.ListVersions("canvas-empty")
|
|
if err != nil {
|
|
t.Fatalf("ListVersions failed: %v", err)
|
|
}
|
|
if len(versions) != 0 {
|
|
t.Errorf("expected 0 versions, got %d", len(versions))
|
|
}
|
|
}
|
|
|
|
// TestCheckCanvasAccess_Owner verifies that the canvas owner gets access.
|
|
func TestCheckCanvasAccess_Owner(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
if err := testDB.AutoMigrate(
|
|
&entity.User{},
|
|
&entity.UserCanvas{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
testDB.Create(&entity.User{ID: "user-1", Nickname: "owner", Email: "a@b.com"})
|
|
testDB.Create(&entity.UserCanvas{ID: "c-1", UserID: "user-1", Title: sptr("My Agent")})
|
|
|
|
svc := NewAgentService()
|
|
ok, err := svc.CheckCanvasAccess("user-1", "c-1")
|
|
if err != nil {
|
|
t.Fatalf("CheckCanvasAccess failed: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Error("expected owner to have access")
|
|
}
|
|
}
|
|
|
|
// TestCheckCanvasAccess_NotOwner verifies that a tenant member can access
|
|
// a team-level canvas.
|
|
func TestCheckCanvasAccess_NotOwner(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
if err := testDB.AutoMigrate(
|
|
&entity.User{},
|
|
&entity.UserCanvas{},
|
|
&entity.UserTenant{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
testDB.Create(&entity.User{ID: "user-1", Nickname: "owner", Email: "a@b.com"})
|
|
testDB.Create(&entity.User{ID: "user-2", Nickname: "member", Email: "c@d.com"})
|
|
// user-2 is a member of user-1's tenant (status "1" = active)
|
|
testDB.Create(&entity.UserTenant{ID: "ut-1", UserID: "user-2", TenantID: "user-1", Role: "member", Status: sptr("1")})
|
|
// Canvas has team-level permission
|
|
testDB.Create(&entity.UserCanvas{ID: "c-1", UserID: "user-1", Permission: "team", Title: sptr("Team Agent")})
|
|
|
|
svc := NewAgentService()
|
|
ok, err := svc.CheckCanvasAccess("user-2", "c-1")
|
|
if err != nil {
|
|
t.Fatalf("CheckCanvasAccess failed: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Error("expected tenant member to have access to team canvas")
|
|
}
|
|
}
|
|
|
|
// TestCheckCanvasAccess_PrivateCanvas_Denied verifies that a tenant member
|
|
// cannot access a private (default "me") canvas.
|
|
func TestCheckCanvasAccess_PrivateCanvas_Denied(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
if err := testDB.AutoMigrate(
|
|
&entity.User{},
|
|
&entity.UserCanvas{},
|
|
&entity.UserTenant{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
testDB.Create(&entity.User{ID: "user-1", Nickname: "owner", Email: "a@b.com"})
|
|
testDB.Create(&entity.User{ID: "user-2", Nickname: "member", Email: "c@d.com"})
|
|
// user-2 is a tenant member (status "1" = active)
|
|
testDB.Create(&entity.UserTenant{ID: "ut-1", UserID: "user-2", TenantID: "user-1", Role: "member", Status: sptr("1")})
|
|
// Canvas has default "me" permission (private)
|
|
testDB.Create(&entity.UserCanvas{ID: "c-1", UserID: "user-1", Title: sptr("Private Agent")})
|
|
|
|
svc := NewAgentService()
|
|
ok, err := svc.CheckCanvasAccess("user-2", "c-1")
|
|
if err != nil {
|
|
t.Fatalf("CheckCanvasAccess failed: %v", err)
|
|
}
|
|
if ok {
|
|
t.Error("expected tenant member to be denied access to private canvas")
|
|
}
|
|
}
|
|
|
|
// TestCheckCanvasAccess_NotFound verifies behavior for non-existent canvas.
|
|
func TestCheckCanvasAccess_NotFound(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
if err := testDB.AutoMigrate(
|
|
&entity.User{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
testDB.Create(&entity.User{ID: "user-1", Nickname: "tester", Email: "a@b.com"})
|
|
|
|
svc := NewAgentService()
|
|
_, err := svc.CheckCanvasAccess("user-1", "non-existent")
|
|
if err == nil {
|
|
t.Error("expected error for non-existent canvas")
|
|
}
|
|
}
|
|
|
|
// TestGetVersion_Success verifies getting a specific version by ID.
|
|
func TestGetVersion_Success(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
if err := testDB.AutoMigrate(
|
|
&entity.UserCanvasVersion{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
testDB.Create(&entity.UserCanvasVersion{
|
|
ID: "v1",
|
|
UserCanvasID: "canvas-1",
|
|
Title: sptr("version-1"),
|
|
DSL: entity.JSONMap{"model": "gpt-4"},
|
|
})
|
|
|
|
svc := NewAgentService()
|
|
v, err := svc.GetVersion("canvas-1", "v1")
|
|
if err != nil {
|
|
t.Fatalf("GetVersion failed: %v", err)
|
|
}
|
|
if *v.Title != "version-1" {
|
|
t.Errorf("expected title 'version-1', got %s", *v.Title)
|
|
}
|
|
}
|
|
|
|
// TestGetVersion_WrongCanvas verifies version belonging to another canvas returns error.
|
|
func TestGetVersion_WrongCanvas(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
if err := testDB.AutoMigrate(
|
|
&entity.UserCanvasVersion{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
testDB.Create(&entity.UserCanvasVersion{
|
|
ID: "v1",
|
|
UserCanvasID: "canvas-1",
|
|
Title: sptr("version-1"),
|
|
})
|
|
|
|
svc := NewAgentService()
|
|
_, err := svc.GetVersion("canvas-other", "v1")
|
|
if err == nil {
|
|
t.Error("expected error for version belonging to another canvas")
|
|
}
|
|
}
|
|
|
|
// TestGetVersion_NotFound verifies error for non-existent version.
|
|
func TestGetVersion_NotFound(t *testing.T) {
|
|
testDB := setupServiceTestDB(t)
|
|
t.Helper()
|
|
|
|
if err := testDB.AutoMigrate(
|
|
&entity.UserCanvasVersion{},
|
|
); err != nil {
|
|
t.Fatalf("failed to migrate: %v", err)
|
|
}
|
|
|
|
orig := dao.DB
|
|
dao.DB = testDB
|
|
t.Cleanup(func() { dao.DB = orig })
|
|
|
|
svc := NewAgentService()
|
|
_, err := svc.GetVersion("canvas-1", "non-existent")
|
|
if err == nil {
|
|
t.Error("expected error for non-existent version")
|
|
}
|
|
}
|
|
|
|
// ptr returns a pointer to the given int64.
|
|
func ptr(v int64) *int64 { return &v } |