Align GoChat with Chatwoot frontend contracts

This commit is contained in:
2026-06-13 22:13:32 +08:00
parent 71abf58636
commit d884fdda0a
162 changed files with 9825 additions and 528 deletions
@@ -2,6 +2,7 @@ package v1
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
@@ -15,6 +16,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/llm"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
@@ -33,6 +35,10 @@ type copilotParityFixture struct {
}
func newCopilotParityFixture(t *testing.T) *copilotParityFixture {
return newCopilotParityFixtureWithProvider(t, nil)
}
func newCopilotParityFixtureWithProvider(t *testing.T, provider llm.Provider) *copilotParityFixture {
t.Helper()
gin.SetMode(gin.TestMode)
@@ -66,7 +72,7 @@ func newCopilotParityFixture(t *testing.T) *copilotParityFixture {
messageRepo := repository.NewCopilotMessageRepo(db)
suggestionRepo := repository.NewCopilotSuggestionRepo(db)
assistantRepo := repository.NewCaptainAssistantRepo(db)
handler := NewCopilotHandler(service.NewCopilotService(threadRepo, messageRepo, suggestionRepo, nil, assistantRepo))
handler := NewCopilotHandler(service.NewCopilotService(threadRepo, messageRepo, suggestionRepo, provider, assistantRepo))
fixture := &copilotParityFixture{
db: db,
@@ -237,6 +243,40 @@ func TestCopilotThreadMessagesListAndCreateUseNestedPayloads(t *testing.T) {
require.Len(t, messages, 4)
}
func TestCopilotThreadCreateProviderDisabledReturnsStableUnavailableAssistantMessage(t *testing.T) {
f := newCopilotParityFixture(t)
thread := f.createThread(t, "Need help")
threadID := uintString(uint(thread["id"].(float64)))
w := f.request(f.router, http.MethodGet, f.captainPath("/copilot_threads/"+threadID+"/copilot_messages/"), nil)
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
messages := decodeMap(t, w)["payload"].([]any)
require.Len(t, messages, 2)
assistant := messages[1].(map[string]any)
require.Equal(t, "assistant", assistant["message_type"])
require.Equal(t, service.CopilotUnavailableMessage, assistant["message"].(map[string]any)["content"])
require.Nil(t, assistant["success"])
}
func TestCopilotThreadCreateProviderEnabledPersistsGeneratedAssistantMessage(t *testing.T) {
provider := &copilotFakeProvider{content: "Generated copilot answer"}
f := newCopilotParityFixtureWithProvider(t, provider)
thread := f.createThread(t, "Need help")
require.NotEmpty(t, provider.lastRequest.Messages)
require.Equal(t, "system", provider.lastRequest.Messages[0].Role)
require.Equal(t, "Need help", provider.lastRequest.Messages[len(provider.lastRequest.Messages)-1].Content)
threadID := uintString(uint(thread["id"].(float64)))
w := f.request(f.router, http.MethodGet, f.captainPath("/copilot_threads/"+threadID+"/copilot_messages/"), nil)
require.Equal(t, http.StatusOK, w.Code, w.Body.String())
messages := decodeMap(t, w)["payload"].([]any)
require.Len(t, messages, 2)
assistant := messages[1].(map[string]any)
require.Equal(t, "assistant", assistant["message_type"])
require.Equal(t, "Generated copilot answer", assistant["message"].(map[string]any)["content"])
}
func TestCopilotMessagePayloadUsesThreadPushShape(t *testing.T) {
f := newCopilotParityFixture(t)
thread := f.createThread(t, "Need help")
@@ -312,3 +352,21 @@ func TestCopilotThreadGetAndDeleteAreScoped(t *testing.T) {
w = f.request(f.router, http.MethodGet, path, nil)
require.Equal(t, http.StatusNotFound, w.Code, w.Body.String())
}
type copilotFakeProvider struct {
content string
lastRequest llm.ChatRequest
}
func (p *copilotFakeProvider) ChatCompletion(ctx context.Context, req llm.ChatRequest) (*llm.ChatResponse, error) {
p.lastRequest = req
return &llm.ChatResponse{Choices: []llm.ChatChoice{{Message: llm.ChatMessage{Role: "assistant", Content: p.content}}}}, nil
}
func (p *copilotFakeProvider) CreateEmbedding(ctx context.Context, req llm.EmbeddingRequest) (*llm.EmbeddingResponse, error) {
return &llm.EmbeddingResponse{}, nil
}
func (p *copilotFakeProvider) ChatCompletionStream(ctx context.Context, req llm.ChatRequest, onChunk func(llm.StreamChunk) error) error {
return nil
}