Files
gochat/internal/handler/api/v1/facebook_callbacks_handler_test.go
T

165 lines
7.7 KiB
Go

package v1
import (
"bytes"
"context"
"encoding/json"
"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"
facebookchannel "github.com/gochat/gochat/internal/channel/facebook"
"github.com/gochat/gochat/internal/model"
channelmodel "github.com/gochat/gochat/internal/model/channel"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
)
type fakeFacebookCallbackProvider struct {
longToken string
pages []facebookchannel.FBPageInfo
instagramID string
}
func (f *fakeFacebookCallbackProvider) ExchangeLongLivedUserToken(context.Context, string) (string, error) {
return f.longToken, nil
}
func (f *fakeFacebookCallbackProvider) ListFacebookPages(context.Context, string) ([]facebookchannel.FBPageInfo, error) {
return f.pages, nil
}
func (f *fakeFacebookCallbackProvider) FetchInstagramBusinessAccountID(context.Context, string) (string, error) {
return f.instagramID, nil
}
func setupFacebookCallbackTest(t *testing.T, provider *fakeFacebookCallbackProvider) (*gin.Engine, *gorm.DB) {
t.Helper()
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(&model.Account{}, &model.Inbox{}, &model.WorkingHour{}, &channelmodel.ChannelFacebook{}))
inboxRepo := repository.NewInboxRepo(db)
fbRepo := repository.NewChannelFacebookRepo(db)
inboxSvc := service.NewInboxService(inboxRepo, nil, nil, nil, nil, nil, nil)
fbSvc := service.NewChannelFacebookService(fbRepo)
handler := NewFacebookChannelHandler(fbSvc, nil, inboxSvc, fbRepo, provider)
r := gin.New()
r.POST("/api/v1/accounts/:account_id/callbacks/register_facebook_page", handler.RegisterFacebookPage)
r.GET("/api/v1/accounts/:account_id/callbacks/register_facebook_page", handler.RegisterFacebookPage)
r.POST("/api/v1/accounts/:account_id/callbacks/facebook_pages", handler.FacebookPages)
r.POST("/api/v1/accounts/:account_id/callbacks/facebook_pages.json", handler.FacebookPages)
r.POST("/api/v1/accounts/:account_id/callbacks/reauthorize_page", handler.ReauthorizePage)
return r, db
}
func TestFacebookCallbacks_RegisterFacebookPage(t *testing.T) {
router, db := setupFacebookCallbackTest(t, &fakeFacebookCallbackProvider{instagramID: "ig-123"})
account := model.Account{Name: "Acme", Active: true}
require.NoError(t, db.Create(&account).Error)
body := map[string]any{"user_access_token": "user-token", "page_access_token": "page-token", "page_id": "page-1", "inbox_name": "Facebook"}
rec := performFacebookCallbackRequest(t, router, http.MethodPost, "/api/v1/accounts/1/callbacks/register_facebook_page", body)
require.Equal(t, http.StatusOK, rec.Code)
var payload map[string]any
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
assert.Equal(t, "Facebook", payload["name"])
assert.Equal(t, "facebook", payload["channel_type"])
assert.Equal(t, "page-1", payload["page_id"])
var channel channelmodel.ChannelFacebook
require.NoError(t, db.First(&channel).Error)
assert.Equal(t, account.ID, channel.AccountID)
assert.Equal(t, "user-token", channel.UserAccessToken)
assert.Equal(t, "page-token", channel.PageAccessToken)
assert.Equal(t, "ig-123", channel.InstagramBusinessAccountID)
}
func TestFacebookCallbacks_FacebookPagesMarksExistingPages(t *testing.T) {
provider := &fakeFacebookCallbackProvider{longToken: "long-user-token", pages: []facebookchannel.FBPageInfo{{ID: "page-1", Name: "Existing", AccessToken: "token-1"}, {ID: "page-2", Name: "New", AccessToken: "token-2"}}}
router, db := setupFacebookCallbackTest(t, provider)
account := model.Account{Name: "Acme", Active: true}
require.NoError(t, db.Create(&account).Error)
require.NoError(t, db.Create(&channelmodel.ChannelFacebook{AccountID: account.ID, InboxID: 10, PageID: "page-1", PageAccessToken: "old"}).Error)
rec := performFacebookCallbackRequest(t, router, http.MethodPost, "/api/v1/accounts/1/callbacks/facebook_pages.json", map[string]any{"omniauth_token": "short-token"})
require.Equal(t, http.StatusOK, rec.Code)
var payload struct {
Data struct {
UserAccessToken string `json:"user_access_token"`
PageDetails []struct {
ID string `json:"id"`
Exists bool `json:"exists"`
} `json:"page_details"`
} `json:"data"`
}
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &payload))
assert.Equal(t, "long-user-token", payload.Data.UserAccessToken)
require.Len(t, payload.Data.PageDetails, 2)
assert.True(t, payload.Data.PageDetails[0].Exists)
assert.False(t, payload.Data.PageDetails[1].Exists)
}
func TestFacebookCallbacks_ReauthorizePageUpdatesMatchingPage(t *testing.T) {
provider := &fakeFacebookCallbackProvider{longToken: "long-user-token", instagramID: "ig-456", pages: []facebookchannel.FBPageInfo{{ID: "page-1", Name: "Renamed", AccessToken: "new-page-token"}}}
router, db := setupFacebookCallbackTest(t, provider)
account := model.Account{Name: "Acme", Active: true}
require.NoError(t, db.Create(&account).Error)
inbox := createTestChannelInbox(t, db, account.ID, 0, "Facebook", "facebook", map[string]any{"page_id": "page-1", "page_access_token": "old-page-token"})
channel := channelmodel.ChannelFacebook{AccountID: account.ID, InboxID: inbox.ID, PageID: "page-1", PageAccessToken: "old-page-token", ReauthorizationRequired: true}
require.NoError(t, db.Create(&channel).Error)
inbox.ChannelID = channel.ID
require.NoError(t, db.Save(inbox).Error)
rec := performFacebookCallbackRequest(t, router, http.MethodPost, "/api/v1/accounts/1/callbacks/reauthorize_page", map[string]any{"omniauth_token": "short-token", "inbox_id": inbox.ID})
require.Equal(t, http.StatusOK, rec.Code)
var updated channelmodel.ChannelFacebook
require.NoError(t, db.First(&updated, channel.ID).Error)
assert.Equal(t, "long-user-token", updated.UserAccessToken)
assert.Equal(t, "new-page-token", updated.PageAccessToken)
assert.Equal(t, "Renamed", updated.PageName)
assert.Equal(t, "ig-456", updated.InstagramBusinessAccountID)
assert.False(t, updated.ReauthorizationRequired)
var updatedInbox model.Inbox
require.NoError(t, db.First(&updatedInbox, inbox.ID).Error)
var config map[string]any
require.NoError(t, json.Unmarshal([]byte(updatedInbox.ChannelConfig), &config))
assert.Equal(t, "new-page-token", config["page_access_token"])
}
func TestFacebookCallbacks_ReauthorizePageReturns422WhenPageMissing(t *testing.T) {
provider := &fakeFacebookCallbackProvider{longToken: "long-user-token", pages: []facebookchannel.FBPageInfo{{ID: "other-page", AccessToken: "token"}}}
router, db := setupFacebookCallbackTest(t, provider)
account := model.Account{Name: "Acme", Active: true}
require.NoError(t, db.Create(&account).Error)
inbox := createTestChannelInbox(t, db, account.ID, 0, "Facebook", "facebook", map[string]any{"page_id": "page-1"})
require.NoError(t, db.Create(&channelmodel.ChannelFacebook{AccountID: account.ID, InboxID: inbox.ID, PageID: "page-1", PageAccessToken: "old"}).Error)
rec := performFacebookCallbackRequest(t, router, http.MethodPost, "/api/v1/accounts/1/callbacks/reauthorize_page", map[string]any{"omniauth_token": "short-token", "inbox_id": inbox.ID})
assert.Equal(t, http.StatusUnprocessableEntity, rec.Code)
}
func performFacebookCallbackRequest(t *testing.T, router *gin.Engine, method, path string, body map[string]any) *httptest.ResponseRecorder {
t.Helper()
encoded, err := json.Marshal(body)
require.NoError(t, err)
req := httptest.NewRequest(method, path, bytes.NewReader(encoded))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
return rec
}