182 lines
7.8 KiB
Go
182 lines
7.8 KiB
Go
package v1
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
"github.com/gochat/gochat/internal/service"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func setupCaptainResourceParityTest(t *testing.T) (*gin.Engine, *gorm.DB, *model.Account, *model.Account, *model.CaptainAssistant) {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
dbName := fmt.Sprintf("file:%s?mode=memory&cache=private", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(
|
|
&model.Account{},
|
|
&model.CaptainAssistant{},
|
|
&model.CaptainScenario{},
|
|
&model.CaptainCustomTool{},
|
|
))
|
|
t.Cleanup(func() {
|
|
sqlDB, _ := db.DB()
|
|
sqlDB.Close()
|
|
})
|
|
|
|
account := &model.Account{Name: "Captain Account", Locale: "en", Active: true}
|
|
otherAccount := &model.Account{Name: "Other Account", Locale: "en", Active: true}
|
|
require.NoError(t, db.Create(account).Error)
|
|
require.NoError(t, db.Create(otherAccount).Error)
|
|
assistant := &model.CaptainAssistant{AccountID: account.ID, Name: "Fin", Description: "Support", Config: json.RawMessage(`{}`), Status: model.AssistantStatusActive}
|
|
require.NoError(t, db.Create(assistant).Error)
|
|
|
|
assistantRepo := repository.NewCaptainAssistantRepo(db)
|
|
scenarioRepo := repository.NewCaptainScenarioRepo(db)
|
|
scenarioSvc := service.NewCaptainScenarioService(scenarioRepo, assistantRepo)
|
|
scenarioHandler := NewCaptainScenarioHandler(scenarioSvc)
|
|
|
|
toolRepo := repository.NewCaptainCustomToolRepo(db)
|
|
toolSvc := service.NewCaptainCustomToolService(toolRepo)
|
|
toolHandler := NewCaptainCustomToolHandler(toolSvc)
|
|
|
|
router := gin.New()
|
|
accountGroup := router.Group("/api/v1/accounts/:account_id/captain")
|
|
assistantScenarios := accountGroup.Group("/assistants/:assistant_id/scenarios")
|
|
assistantScenarios.GET("/", scenarioHandler.List)
|
|
assistantScenarios.POST("/", scenarioHandler.Create)
|
|
assistantScenarios.GET("/:scenario_id", scenarioHandler.Get)
|
|
assistantScenarios.PUT("/:scenario_id", scenarioHandler.Update)
|
|
assistantScenarios.DELETE("/:scenario_id", scenarioHandler.Delete)
|
|
|
|
customTools := accountGroup.Group("/custom_tools")
|
|
customTools.GET("/", toolHandler.List)
|
|
customTools.POST("/", toolHandler.Create)
|
|
customTools.GET("/:tool_id", toolHandler.Get)
|
|
customTools.PUT("/:tool_id", toolHandler.Update)
|
|
customTools.DELETE("/:tool_id", toolHandler.Delete)
|
|
|
|
return router, db, account, otherAccount, assistant
|
|
}
|
|
|
|
func captainResourceJSONRequest(t *testing.T, router *gin.Engine, method, path string, body any) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
var payload []byte
|
|
if body != nil {
|
|
data, err := json.Marshal(body)
|
|
require.NoError(t, err)
|
|
payload = data
|
|
}
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(method, path, bytes.NewReader(payload))
|
|
if body != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
router.ServeHTTP(w, req)
|
|
return w
|
|
}
|
|
|
|
func TestCaptainScenarioHandler_ChatwootScenarioPayloadsAndScope(t *testing.T) {
|
|
router, db, account, otherAccount, assistant := setupCaptainResourceParityTest(t)
|
|
basePath := "/api/v1/accounts/" + strconv.FormatUint(uint64(account.ID), 10) + "/captain/assistants/" + strconv.FormatUint(uint64(assistant.ID), 10) + "/scenarios"
|
|
otherBasePath := "/api/v1/accounts/" + strconv.FormatUint(uint64(otherAccount.ID), 10) + "/captain/assistants/" + strconv.FormatUint(uint64(assistant.ID), 10) + "/scenarios"
|
|
|
|
body := map[string]any{"scenario": map[string]any{
|
|
"title": "Escalate billing",
|
|
"description": "Billing handoff",
|
|
"instruction": "Ask for invoice ID",
|
|
"tools": []string{"handoff"},
|
|
}}
|
|
w := captainResourceJSONRequest(t, router, http.MethodPost, basePath+"/", body)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var created map[string]any
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &created))
|
|
assert.NotContains(t, created, "success")
|
|
assert.Equal(t, "Escalate billing", created["title"])
|
|
assert.Equal(t, float64(account.ID), created["account_id"])
|
|
assert.Equal(t, float64(assistant.ID), created["assistant_id"])
|
|
assert.Equal(t, "Fin", created["assistant"].(map[string]any)["name"])
|
|
scenarioID := uint(created["id"].(float64))
|
|
|
|
disabled := &model.CaptainScenario{AccountID: account.ID, AssistantID: assistant.ID, Title: "Disabled", Enabled: false}
|
|
require.NoError(t, db.Create(disabled).Error)
|
|
require.NoError(t, db.Model(disabled).Update("enabled", false).Error)
|
|
w = captainResourceJSONRequest(t, router, http.MethodGet, basePath+"/", nil)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var listResp map[string]any
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &listResp))
|
|
assert.Len(t, listResp["payload"], 1)
|
|
assert.Equal(t, float64(1), listResp["meta"].(map[string]any)["total_count"])
|
|
|
|
w = captainResourceJSONRequest(t, router, http.MethodGet, fmt.Sprintf("%s/%d", otherBasePath, scenarioID), nil)
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
|
|
updateBody := map[string]any{"scenario": map[string]any{"enabled": false, "instruction": "Updated"}}
|
|
w = captainResourceJSONRequest(t, router, http.MethodPut, fmt.Sprintf("%s/%d", basePath, scenarioID), updateBody)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var updated map[string]any
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &updated))
|
|
assert.Equal(t, false, updated["enabled"])
|
|
assert.Equal(t, "Updated", updated["instruction"])
|
|
|
|
w = captainResourceJSONRequest(t, router, http.MethodDelete, fmt.Sprintf("%s/%d", basePath, scenarioID), nil)
|
|
assert.Equal(t, http.StatusNoContent, w.Code)
|
|
}
|
|
|
|
func TestCaptainCustomToolHandler_ChatwootToolPayloadsAndScope(t *testing.T) {
|
|
router, _, account, otherAccount, _ := setupCaptainResourceParityTest(t)
|
|
basePath := "/api/v1/accounts/" + strconv.FormatUint(uint64(account.ID), 10) + "/captain/custom_tools"
|
|
otherBasePath := "/api/v1/accounts/" + strconv.FormatUint(uint64(otherAccount.ID), 10) + "/captain/custom_tools"
|
|
|
|
body := map[string]any{"custom_tool": map[string]any{
|
|
"title": "Lookup Order",
|
|
"description": "Fetch order status",
|
|
"endpoint_url": "https://example.com/orders",
|
|
"http_method": "POST",
|
|
"auth_type": "none",
|
|
"param_schema": []map[string]any{{"name": "order_id", "type": "string", "required": true}},
|
|
"request_template": "{\"id\":\"{{.order_id}}\"}",
|
|
}}
|
|
w := captainResourceJSONRequest(t, router, http.MethodPost, basePath+"/", body)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var created map[string]any
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &created))
|
|
assert.NotContains(t, created, "success")
|
|
assert.Equal(t, "lookup-order", created["slug"])
|
|
assert.Equal(t, "POST", created["http_method"])
|
|
toolID := uint(created["id"].(float64))
|
|
|
|
w = captainResourceJSONRequest(t, router, http.MethodGet, basePath+"/", nil)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var listResp map[string]any
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &listResp))
|
|
assert.Len(t, listResp["payload"], 1)
|
|
assert.Equal(t, float64(1), listResp["meta"].(map[string]any)["page"])
|
|
|
|
w = captainResourceJSONRequest(t, router, http.MethodGet, fmt.Sprintf("%s/%d", otherBasePath, toolID), nil)
|
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
|
|
|
updateBody := map[string]any{"custom_tool": map[string]any{"enabled": false, "title": "Lookup Order V2"}}
|
|
w = captainResourceJSONRequest(t, router, http.MethodPut, fmt.Sprintf("%s/%d", basePath, toolID), updateBody)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
var updated map[string]any
|
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &updated))
|
|
assert.Equal(t, false, updated["enabled"])
|
|
assert.Equal(t, "Lookup Order V2", updated["title"])
|
|
|
|
w = captainResourceJSONRequest(t, router, http.MethodDelete, fmt.Sprintf("%s/%d", basePath, toolID), nil)
|
|
assert.Equal(t, http.StatusNoContent, w.Code)
|
|
}
|