Files
gochat/internal/handler/api/v1/platform_handler_test.go_BAK
T
2026-06-04 15:44:48 +08:00

505 lines
15 KiB
Plaintext

package v1
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
)
// setupPlatformAppTestEnv creates DB + repo + service + handler + router for testing.
func setupPlatformAppTestEnv(t *testing.T) (*gorm.DB, *service.PlatformAppService, *PlatformAppHandler, *gin.Engine) {
t.Helper()
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
require.NoError(t, err, "failed to open SQLite test DB")
require.NoError(t, db.AutoMigrate(&model.PlatformApp{}, &model.Account{}), "failed to migrate models")
t.Cleanup(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
repo := repository.NewPlatformAppRepo(db)
svc := service.NewPlatformAppService(repo)
handler := NewPlatformAppHandler(svc)
router := gin.New()
router.GET("/platform/api/v1/apps", handler.List)
router.POST("/platform/api/v1/apps", handler.Create)
router.GET("/platform/api/v1/apps/search", handler.Search)
router.GET("/platform/api/v1/apps/:id", handler.Get)
router.PUT("/platform/api/v1/apps/:id", handler.Update)
router.DELETE("/platform/api/v1/apps/:id", handler.Delete)
router.POST("/platform/api/v1/apps/:id/regenerate_api_key", handler.RegenerateAPIKey)
accounts := router.Group("/api/v1/accounts/:account_id")
accounts.GET("/platform_apps", handler.List)
accounts.POST("/platform_apps", handler.Create)
accounts.GET("/platform_apps/search", handler.Search)
accounts.GET("/platform_apps/:id", handler.Get)
accounts.PUT("/platform_apps/:id", handler.Update)
accounts.DELETE("/platform_apps/:id", handler.Delete)
accounts.POST("/platform_apps/:id/regenerate_api_key", handler.RegenerateAPIKey)
return db, svc, handler, router
}
// createHandlerTestAccount creates a test account directly in DB.
func createHandlerTestAccount(t *testing.T, db *gorm.DB) *model.Account {
t.Helper()
account := &model.Account{Name: "HandlerTestAccount", Locale: "en", Status: "active"}
require.NoError(t, db.Create(account).Error)
return account
}
// ========== List (Platform-level) ==========
func TestPlatformAppHandler_List_PlatformLevel(t *testing.T) {
db, svc, _, router := setupPlatformAppTestEnv(t)
account := createHandlerTestAccount(t, db)
// Create test apps via service
_, err := svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "PlatformApp1", Type: "api", AccountID: account.ID,
})
require.NoError(t, err)
_, err = svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "PlatformApp2", Type: "api",
})
require.NoError(t, err)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/platform/api/v1/apps", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
assert.True(t, resp["success"].(bool))
meta := resp["meta"].(map[string]interface{})
assert.Equal(t, float64(2), meta["total_count"])
}
func TestPlatformAppHandler_List_AccountScoped(t *testing.T) {
db, svc, _, router := setupPlatformAppTestEnv(t)
account := createHandlerTestAccount(t, db)
_, err := svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "ScopedApp", Type: "api", AccountID: account.ID,
})
require.NoError(t, err)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/platform_apps", account.ID), nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
assert.True(t, resp["success"].(bool))
}
func TestPlatformAppHandler_List_InvalidAccountID(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/notanumber/platform_apps", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== Create ==========
func TestPlatformAppHandler_Create_Success(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
body := map[string]interface{}{
"name": "NewPlatformApp",
"description": "A brand new app",
"type": "api",
}
bodyBytes, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/platform/api/v1/apps", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
assert.True(t, resp["success"].(bool))
data := resp["data"].(map[string]interface{})
assert.Equal(t, "NewPlatformApp", data["name"])
assert.NotEmpty(t, data["api_key"])
assert.Equal(t, "api", data["type"])
assert.Equal(t, "active", data["status"])
}
func TestPlatformAppHandler_Create_AccountScoped(t *testing.T) {
db, _, _, router := setupPlatformAppTestEnv(t)
account := createHandlerTestAccount(t, db)
body := map[string]interface{}{
"name": "AccountScopedApp",
"type": "api",
}
bodyBytes, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/platform_apps", account.ID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
data := resp["data"].(map[string]interface{})
// AccountID should be set from URL param
assert.Equal(t, float64(account.ID), data["account_id"])
}
func TestPlatformAppHandler_Create_ValidationError(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
// Missing required "name" field
body := map[string]interface{}{
"type": "api",
}
bodyBytes, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/platform/api/v1/apps", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestPlatformAppHandler_Create_InvalidJSON(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/platform/api/v1/apps", bytes.NewReader([]byte("not json")))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== Get ==========
func TestPlatformAppHandler_Get_Success(t *testing.T) {
_, svc, _, router := setupPlatformAppTestEnv(t)
app, err := svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "GetTestApp", Type: "api",
})
require.NoError(t, err)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/platform/api/v1/apps/%d", app.ID), nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
data := resp["data"].(map[string]interface{})
assert.Equal(t, "GetTestApp", data["name"])
}
func TestPlatformAppHandler_Get_NotFound(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/platform/api/v1/apps/99999", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestPlatformAppHandler_Get_InvalidID(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/platform/api/v1/apps/abc", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== Update ==========
func TestPlatformAppHandler_Update_Success(t *testing.T) {
_, svc, _, router := setupPlatformAppTestEnv(t)
app, err := svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "OriginalName", Type: "api",
})
require.NoError(t, err)
body := map[string]interface{}{
"name": "UpdatedName",
"description": "Updated desc",
}
bodyBytes, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", fmt.Sprintf("/platform/api/v1/apps/%d", app.ID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
data := resp["data"].(map[string]interface{})
assert.Equal(t, "UpdatedName", data["name"])
}
func TestPlatformAppHandler_Update_Status(t *testing.T) {
_, svc, _, router := setupPlatformAppTestEnv(t)
app, err := svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "StatusApp", Type: "api",
})
require.NoError(t, err)
body := map[string]interface{}{
"status": "disabled",
}
bodyBytes, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", fmt.Sprintf("/platform/api/v1/apps/%d", app.ID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
data := resp["data"].(map[string]interface{})
assert.Equal(t, "disabled", data["status"])
}
func TestPlatformAppHandler_Update_NotFound(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
body := map[string]interface{}{
"name": "NewName",
}
bodyBytes, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/platform/api/v1/apps/99999", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestPlatformAppHandler_Update_InvalidID(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
body := map[string]interface{}{
"name": "NewName",
}
bodyBytes, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "/platform/api/v1/apps/abc", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestPlatformAppHandler_Update_ValidationError(t *testing.T) {
_, svc, _, router := setupPlatformAppTestEnv(t)
app, err := svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "ValidApp", Type: "api",
})
require.NoError(t, err)
// Invalid type value
body := map[string]interface{}{
"type": "invalid_type",
}
bodyBytes, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", fmt.Sprintf("/platform/api/v1/apps/%d", app.ID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== Delete ==========
func TestPlatformAppHandler_Delete_Success(t *testing.T) {
_, svc, _, router := setupPlatformAppTestEnv(t)
app, err := svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "DeleteApp", Type: "api",
})
require.NoError(t, err)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", fmt.Sprintf("/platform/api/v1/apps/%d", app.ID), nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNoContent, w.Code)
}
func TestPlatformAppHandler_Delete_InvalidID(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/platform/api/v1/apps/abc", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== RegenerateAPIKey ==========
func TestPlatformAppHandler_RegenerateAPIKey_Success(t *testing.T) {
_, svc, _, router := setupPlatformAppTestEnv(t)
app, err := svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "KeyRotateApp", Type: "api",
})
require.NoError(t, err)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", fmt.Sprintf("/platform/api/v1/apps/%d/regenerate_api_key", app.ID), nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
data := resp["data"].(map[string]interface{})
newKey := data["api_key"].(string)
assert.NotEqual(t, app.APIKey, newKey, "regenerated key should differ from original")
assert.NotEmpty(t, newKey)
assert.Equal(t, float64(app.ID), data["id"])
}
func TestPlatformAppHandler_RegenerateAPIKey_InvalidID(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/platform/api/v1/apps/abc/regenerate_api_key", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestPlatformAppHandler_RegenerateAPIKey_NotFound(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/platform/api/v1/apps/99999/regenerate_api_key", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
// ========== Search ==========
func TestPlatformAppHandler_Search_PlatformWide(t *testing.T) {
_, svc, _, router := setupPlatformAppTestEnv(t)
svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "SearchApp1", Type: "api",
})
svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "SearchApp2", Type: "api",
})
// Empty query — should list all
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/platform/api/v1/apps/search?q=", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestPlatformAppHandler_Search_WithQuery(t *testing.T) {
_, svc, _, router := setupPlatformAppTestEnv(t)
svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "AlphaSearchApp", Type: "api",
})
svc.Create(context.Background(), service.CreatePlatformAppRequest{
Name: "BetaSearchApp", Type: "api",
})
// Search by name — ILIKE requires PG, may fail on SQLite
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/platform/api/v1/apps/search?q=Alpha", nil)
router.ServeHTTP(w, req)
// If ILIKE fails (SQLite), we get 500; if PG, we get 200 with filtered results
if w.Code == http.StatusInternalServerError {
t.Skip("skip: SQLite does not support ILIKE (Search endpoint needs PG)")
}
assert.Equal(t, http.StatusOK, w.Code)
}
func TestPlatformAppHandler_Search_AccountScoped(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
// Empty query account-scoped — should list all for account
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/platform_apps/search?q=", nil)
router.ServeHTTP(w, req)
// Account 1 has no apps, so 200 with empty meta
assert.Equal(t, http.StatusOK, w.Code)
}
func TestPlatformAppHandler_Search_AccountScoped_InvalidAccountID(t *testing.T) {
_, _, _, router := setupPlatformAppTestEnv(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/platform_apps/search?q=test", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}